light_sdk/
transfer.rs

1use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo;
2
3use crate::error::{LightSdkError, Result};
4
5/// Transfers a specified amount of lamports from one account to another.
6///
7/// Attempts to transfer `lamports` from the `from` account to the `to`
8/// account. It will update the lamport balances of both accounts if the
9/// transfer is successful.
10pub fn transfer_compressed_sol(
11    from: &mut CompressedAccountInfo,
12    to: &mut CompressedAccountInfo,
13    lamports: u64,
14) -> Result<()> {
15    if let Some(output) = from.output.as_mut() {
16        output.lamports = output
17            .lamports
18            .checked_sub(lamports)
19            .ok_or(LightSdkError::TransferFromInsufficientLamports)?;
20    }
21    // Issue:
22    // - we must not modify the balance of the input account since we need the correct value
23    //  to verify the proof.
24    // - If an account has no output compressed account have to transfer all lamports from it.
25    // - However the account does not have an output balance to measure the difference.
26    // - We could solve this by using the output balance anyway but skipping output values
27    //      which only use lamports and no data (program owned compressed must have data).
28    // else if let Some(input) = from.input.as_mut() {
29    //     input.lamports = input
30    //         .lamports
31    //         .checked_sub(lamports)
32    //         .ok_or(LightSdkError::TransferFromInsufficientLamports)?
33    // }
34    else {
35        return Err(LightSdkError::TransferFromNoLamports);
36    };
37
38    if let Some(output) = to.output.as_mut() {
39        output.lamports = output
40            .lamports
41            .checked_add(lamports)
42            .ok_or(LightSdkError::TransferIntegerOverflow)?;
43    } else {
44        return Err(LightSdkError::TransferFromNoLamports);
45    }
46
47    Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52    use light_compressed_account::{
53        compressed_account::PackedMerkleContext,
54        instruction_data::with_account_info::{
55            CompressedAccountInfo, InAccountInfo, OutAccountInfo,
56        },
57    };
58
59    use super::*;
60    use crate::Pubkey;
61
62    /// Creates a mock account with the given input lamports.
63    fn mock_account(_owner: &Pubkey, lamports: Option<u64>) -> CompressedAccountInfo {
64        let input_lamports = lamports.unwrap_or(0);
65        CompressedAccountInfo {
66            input: Some(InAccountInfo {
67                lamports: input_lamports,
68                // None of the following values matter.
69                data_hash: [0; 32],
70                merkle_context: PackedMerkleContext {
71                    merkle_tree_pubkey_index: 0,
72                    queue_pubkey_index: 0,
73                    leaf_index: 0,
74                    prove_by_index: false,
75                },
76                // None of the following values matter.
77                discriminator: [0; 8],
78                root_index: 0,
79            }),
80            output: Some(OutAccountInfo {
81                lamports: input_lamports,
82                // None of the following values matter.
83                data_hash: [0; 32],
84                data: Vec::new(),
85                output_merkle_tree_index: 0,
86                // None of the following values matter.
87                discriminator: [0; 8],
88            }),
89            address: Some([1; 32]),
90        }
91    }
92
93    /// Creates a mock account without input.
94    fn mock_account_without_input(_owner: &Pubkey) -> CompressedAccountInfo {
95        CompressedAccountInfo {
96            input: None,
97            output: Some(OutAccountInfo {
98                lamports: 0,
99                // None of the following values matter.
100                data_hash: [0; 32],
101                data: Vec::new(),
102                output_merkle_tree_index: 0,
103                // None of the following values matter.
104                discriminator: [0; 8],
105            }),
106            address: Some([1; 32]),
107        }
108    }
109
110    #[test]
111    fn test_transfer_success() {
112        let from_pubkey = Pubkey::new_unique();
113        let mut from = mock_account(&from_pubkey, Some(1000));
114        let to_pubkey = Pubkey::new_unique();
115        let mut to = mock_account(&to_pubkey, Some(500));
116
117        let result = transfer_compressed_sol(&mut from, &mut to, 300);
118        assert!(result.is_ok());
119        assert_eq!(from.output.as_ref().unwrap().lamports, 700);
120        assert_eq!(to.output.as_ref().unwrap().lamports, 800);
121    }
122
123    #[test]
124    fn test_transfer_from_no_input() {
125        let from_pubkey = Pubkey::new_unique();
126        let mut from = mock_account_without_input(&from_pubkey);
127        let to_pubkey = Pubkey::new_unique();
128        let mut to = mock_account(&to_pubkey, Some(500));
129
130        let result = transfer_compressed_sol(&mut from, &mut to, 300);
131        assert_eq!(result, Err(LightSdkError::TransferFromInsufficientLamports));
132    }
133
134    #[test]
135    fn test_transfer_from_no_lamports() {
136        let from_pubkey = Pubkey::new_unique();
137        let mut from = mock_account(&from_pubkey, None);
138        let to_pubkey = Pubkey::new_unique();
139        let mut to = mock_account(&to_pubkey, Some(500));
140
141        let result = transfer_compressed_sol(&mut from, &mut to, 300);
142        assert_eq!(result, Err(LightSdkError::TransferFromInsufficientLamports));
143    }
144
145    #[test]
146    fn test_transfer_insufficient_lamports() {
147        let from_pubkey = Pubkey::new_unique();
148        let mut from = mock_account(&from_pubkey, Some(200));
149        let to_pubkey = Pubkey::new_unique();
150        let mut to = mock_account(&to_pubkey, Some(500));
151
152        let result = transfer_compressed_sol(&mut from, &mut to, 300);
153        assert_eq!(result, Err(LightSdkError::TransferFromInsufficientLamports));
154    }
155
156    #[test]
157    fn test_transfer_integer_overflow() {
158        let from_pubkey = Pubkey::new_unique();
159        let mut from = mock_account(&from_pubkey, Some(1000));
160        let to_pubkey = Pubkey::new_unique();
161        let mut to = mock_account(&to_pubkey, Some(u64::MAX - 500));
162
163        let result = transfer_compressed_sol(&mut from, &mut to, 600);
164        assert_eq!(result, Err(LightSdkError::TransferIntegerOverflow));
165    }
166
167    #[test]
168    fn test_transfer_to_no_lamports() {
169        let from_pubkey = Pubkey::new_unique();
170        let mut from = mock_account(&from_pubkey, Some(1000));
171        let to_pubkey = Pubkey::new_unique();
172        let mut to = mock_account(&to_pubkey, Some(0));
173        to.output.as_mut().unwrap().lamports = 0;
174
175        let result = transfer_compressed_sol(&mut from, &mut to, 500);
176        assert!(result.is_ok());
177        assert_eq!(from.output.as_ref().unwrap().lamports, 500);
178        assert_eq!(to.output.as_ref().unwrap().lamports, 500);
179    }
180}