hodor_program/swap/
processor.rs

1use solana_program::account_info::{AccountInfo, next_account_info};
2use solana_program::entrypoint::ProgramResult;
3use solana_program::msg;
4use solana_program::program::{invoke, invoke_signed};
5use solana_program::pubkey::Pubkey;
6use solana_program::program_pack::Pack;
7use solana_program::rent::Rent;
8use solana_program::sysvar::Sysvar;
9use spl_token::state::Mint;
10use solana_program::program_error::ProgramError::{IllegalOwner, InvalidAccountData, InvalidInstructionData, MissingRequiredSignature};
11use crate::swap::state::{CreatorFee, SwapPool};
12use crate::swap::instruction::{calculate_deposit_amounts, calculate_swap_amounts, calculate_withdraw_amounts, SwapInstruction};
13use crate::processor::{create_spl_token_account, transfer_spl_token};
14
15pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
16    match SwapInstruction::unpack(instruction_data)? {
17        SwapInstruction::CreatePool { seed, lp_fee_rate, creator_fee_rate } => {
18            msg!("Swap:CreatePool");
19            process_create_pool(program_id, accounts, seed, lp_fee_rate, creator_fee_rate)
20        }
21        SwapInstruction::Swap { in_amount, min_out_amount } => {
22            msg!("Swap:Swap");
23            process_swap(program_id, accounts, in_amount, min_out_amount)
24        }
25        SwapInstruction::Deposit { min_a, max_a, min_b, max_b } => {
26            msg!("Swap:Deposit");
27            process_deposit(program_id, accounts, min_a, max_a, min_b, max_b)
28        }
29        SwapInstruction::Withdraw { lp_amount, min_a, min_b } => {
30            msg!("Swap:Withdraw");
31            process_wthdraw(program_id, accounts, lp_amount, min_a, min_b)
32        }
33    }
34}
35
36fn process_create_pool(program_id: &Pubkey, accounts: &[AccountInfo], seed: [u8; 32],
37                       lp_fee_rate: u32, creator_fee_rate: u32) -> ProgramResult {
38    let accounts_iter = &mut accounts.iter();
39
40    let fee_payer_info = next_account_info(accounts_iter)?;
41    let swap_state_info = next_account_info(accounts_iter)?;
42    let token_a_mint_info = next_account_info(accounts_iter)?;
43    let token_a_account_info = next_account_info(accounts_iter)?;
44    let token_b_mint_info = next_account_info(accounts_iter)?;
45    let token_b_account_info = next_account_info(accounts_iter)?;
46    let lp_mint_info = next_account_info(accounts_iter)?;
47
48    let spl_token_program = next_account_info(accounts_iter)?;
49    let system_program = next_account_info(accounts_iter)?;
50
51    let rent = Rent::get()?;
52
53    if !fee_payer_info.is_signer {
54        return Err(MissingRequiredSignature);
55    }
56
57    if token_a_mint_info.key == token_b_mint_info.key {
58        return Err(InvalidAccountData);
59    }
60
61    let seeds_a = [swap_state_info.key.as_ref(), b"A"];
62    create_spl_token_account(
63        token_a_account_info,
64        token_a_mint_info,
65        swap_state_info,
66        fee_payer_info,
67        &seeds_a,
68        program_id,
69        spl_token_program,
70        system_program,
71    )?;
72
73    let seeds_b = [swap_state_info.key.as_ref(), b"B"];
74    create_spl_token_account(
75        token_b_account_info,
76        token_b_mint_info,
77        swap_state_info,
78        fee_payer_info,
79        &seeds_b,
80        program_id,
81        spl_token_program,
82        system_program,
83    )?;
84
85    // Creating new mint for LP token
86    let seeds_mint = [swap_state_info.key.as_ref(), b"LP"];
87    let (lp_mint_account, bump_seed) = Pubkey::find_program_address(&seeds_mint, program_id);
88
89    let create_mint_account_instruction = solana_program::system_instruction::create_account(
90        &fee_payer_info.key,
91        &lp_mint_account,
92        rent.minimum_balance(spl_token::state::Mint::LEN),
93        spl_token::state::Mint::LEN as u64,
94        &spl_token::id(),
95    );
96    invoke_signed(
97        &create_mint_account_instruction,
98        &[
99            system_program.clone(),
100            fee_payer_info.clone(),
101            lp_mint_info.clone(),
102        ],
103        &[&[&seeds_mint[0], &seeds_mint[1], &[bump_seed]]], // todo seeds explode
104    )?;
105
106    let initialize_mint_instruction = spl_token::instruction::initialize_mint2(
107        &spl_token_program.key,
108        &lp_mint_account,
109        &swap_state_info.key,
110        None,
111        6,
112    )?;
113
114    invoke(
115        &initialize_mint_instruction,
116        &[
117            spl_token_program.clone(),
118            lp_mint_info.clone(),
119        ],
120    )?;
121
122    let (creator_fee, state_size) = {
123        if creator_fee_rate > 0 {
124            (Some(CreatorFee {
125                rate: creator_fee_rate,
126                balance_a: 0,
127                balance_b: 0,
128                withdraw_authority: fee_payer_info.key.clone(),
129            }), SwapPool::WITH_CREATOR_FEE_SIZE)
130        } else {
131            (None, SwapPool::BASE_SIZE)
132        }
133    };
134
135
136    let create_state_account_instruction = solana_program::system_instruction::create_account(
137        &fee_payer_info.key,
138        &swap_state_info.key,
139        rent.minimum_balance(state_size),
140        state_size as u64,
141        &program_id,
142    );
143
144    // todo: test making sure it fails if account exists or if seed is incorrect
145    invoke_signed(
146        &create_state_account_instruction,
147        &[
148            system_program.clone(),
149            fee_payer_info.clone(),
150            swap_state_info.clone(),
151        ],
152        &[&[&seed]],
153    )?;
154
155    SwapPool {
156        seed: seed,
157        token_account_a: *token_a_account_info.key,
158        token_account_b: *token_b_account_info.key,
159        balance_a: 0,
160        balance_b: 0,
161        lp_mint: lp_mint_account,
162        lp_fee_rate,
163        creator_fee: creator_fee,
164    }.pack(&mut swap_state_info.try_borrow_mut_data()?)?;
165
166    Ok(())
167}
168
169
170fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo], min_a: u64, max_a: u64, min_b: u64, max_b: u64)
171                   -> ProgramResult {
172    let accounts_iter = &mut accounts.iter();
173    let owner_info = next_account_info(accounts_iter)?;
174    let swap_pool_state_info = next_account_info(accounts_iter)?;
175    let source_a_info = next_account_info(accounts_iter)?;
176    let destination_a_info = next_account_info(accounts_iter)?;
177    let source_b_info = next_account_info(accounts_iter)?;
178    let destination_b_info = next_account_info(accounts_iter)?;
179    let lp_mint_info = next_account_info(accounts_iter)?;
180    let destination_lp_info = next_account_info(accounts_iter)?;
181
182    let spl_token_program = next_account_info(accounts_iter)?;
183
184    if !owner_info.is_signer {
185        return Err(MissingRequiredSignature);
186    }
187
188    if swap_pool_state_info.owner != program_id {
189        return Err(IllegalOwner);
190    }
191
192    let mut swap_pool_state = SwapPool::unpack(&swap_pool_state_info.try_borrow_data()?)?;
193    if swap_pool_state.token_account_a != *destination_a_info.key
194        || swap_pool_state.token_account_b != *destination_b_info.key
195        || swap_pool_state.lp_mint != *lp_mint_info.key {
196        return Err(InvalidAccountData);
197    }
198
199    let lp_mint_state = Mint::unpack(&lp_mint_info.try_borrow_data()?)?;
200
201    let (token_a_transfer_amount, token_b_transfer_amount, lp_mint_amount) = calculate_deposit_amounts(
202        swap_pool_state.balance_a,
203        swap_pool_state.balance_b,
204        lp_mint_state.supply,
205        max_a,
206        max_b).ok_or(InvalidInstructionData)?;
207
208    if token_a_transfer_amount < min_a || token_b_transfer_amount < min_b {
209        // todo: add custom error message
210        return Err(InvalidInstructionData);
211    }
212
213    transfer_spl_token(
214        source_a_info,
215        destination_a_info,
216        owner_info,
217        spl_token_program,
218        token_a_transfer_amount,
219    )?;
220
221    transfer_spl_token(
222        source_b_info,
223        destination_b_info,
224        owner_info,
225        spl_token_program,
226        token_b_transfer_amount,
227    )?;
228
229    let mint_instruction = spl_token::instruction::mint_to(
230        spl_token_program.key,
231        &swap_pool_state.lp_mint,
232        destination_lp_info.key,
233        swap_pool_state_info.key,
234        &[],
235        lp_mint_amount,
236    )?;
237
238    invoke_signed(
239        &mint_instruction,
240        &[
241            spl_token_program.clone(),
242            lp_mint_info.clone(),
243            destination_lp_info.clone(),
244            swap_pool_state_info.clone(),
245        ],
246        &[&[&swap_pool_state.seed]],
247    )?;
248
249
250    swap_pool_state.balance_a = swap_pool_state.balance_a
251        .checked_add(token_a_transfer_amount)
252        .ok_or(InvalidInstructionData)?;
253    swap_pool_state.balance_b = swap_pool_state.balance_b
254        .checked_add(token_b_transfer_amount)
255        .ok_or(InvalidInstructionData)?;
256    swap_pool_state.pack(&mut swap_pool_state_info.try_borrow_mut_data()?)?;
257
258    Ok(())
259}
260
261fn process_swap(program_id: &Pubkey, accounts: &[AccountInfo], in_amount: u64, min_out_amount: u64) -> ProgramResult {
262    let accounts_iter = &mut accounts.iter();
263    let owner_info = next_account_info(accounts_iter)?;
264    let swap_pool_state_info = next_account_info(accounts_iter)?;
265    let input_source_info = next_account_info(accounts_iter)?;
266    let input_destination_info = next_account_info(accounts_iter)?;
267    let output_source_info = next_account_info(accounts_iter)?;
268    let output_destination_info = next_account_info(accounts_iter)?;
269
270    let spl_token_program = next_account_info(accounts_iter)?;
271
272    if !owner_info.is_signer {
273        return Err(MissingRequiredSignature);
274    }
275
276    if swap_pool_state_info.owner != program_id {
277        return Err(IllegalOwner);
278    }
279
280    if in_amount == 0 {
281        return Err(InvalidInstructionData);
282    }
283
284    let mut swap_pool_state = SwapPool::unpack(&swap_pool_state_info.try_borrow_data()?)?;
285
286    // todo: this conditions need to be unit tested
287    let is_a_to_b = {
288        if *input_destination_info.key == swap_pool_state.token_account_a
289            && *output_source_info.key == swap_pool_state.token_account_b {
290            true
291        } else if *input_destination_info.key == swap_pool_state.token_account_b
292            && *output_source_info.key == swap_pool_state.token_account_a {
293            false
294        } else {
295            return Err(InvalidAccountData);
296        }
297    };
298
299    let (pool_balance_in_token, pool_balance_out_token) = if is_a_to_b {
300        (swap_pool_state.balance_a, swap_pool_state.balance_b)
301    } else {
302        (swap_pool_state.balance_b, swap_pool_state.balance_a)
303    };
304
305    let (out_amount, dao_fee_amount, _lp_fee_amount, creator_fee_amount) = calculate_swap_amounts(
306        pool_balance_in_token,
307        pool_balance_out_token,
308        in_amount,
309        50_000, // hardcoded 0.05%, todo: read from dao controlled config account
310        swap_pool_state.lp_fee_rate,
311        swap_pool_state.creator_fee.as_ref()
312            .map_or(0, |cf| cf.rate),
313    ).ok_or(InvalidInstructionData)?;
314
315    if out_amount < min_out_amount || out_amount == 0 {
316        // todo: add custom error message
317        return Err(InvalidInstructionData);
318    }
319
320    transfer_spl_token(
321        input_source_info,
322        input_destination_info,
323        owner_info,
324        spl_token_program,
325        in_amount,
326    )?;
327
328    let withdraw_transfer_instruction = spl_token::instruction::transfer(
329        spl_token_program.key,
330        output_source_info.key,
331        output_destination_info.key,
332        swap_pool_state_info.key,
333        &[],
334        out_amount,
335    )?;
336    invoke_signed(
337        &withdraw_transfer_instruction,
338        &[
339            spl_token_program.clone(),
340            output_source_info.clone(),
341            output_destination_info.clone(),
342            swap_pool_state_info.clone(),
343        ],
344        &[&[&swap_pool_state.seed]],
345    )?;
346
347    let pool_deposit_amount = in_amount
348        .checked_sub(dao_fee_amount)
349        .ok_or(InvalidInstructionData)?
350        .checked_sub(creator_fee_amount)
351        .ok_or(InvalidInstructionData)?;
352
353    if is_a_to_b {
354        swap_pool_state.balance_a = swap_pool_state.balance_a
355            .checked_add(pool_deposit_amount)
356            .ok_or(InvalidInstructionData)?;
357        swap_pool_state.balance_b = swap_pool_state.balance_b
358            .checked_sub(out_amount)
359            .ok_or(InvalidInstructionData)?;
360    } else {
361        swap_pool_state.balance_b = swap_pool_state.balance_b
362            .checked_add(pool_deposit_amount)
363            .ok_or(InvalidInstructionData)?;
364        swap_pool_state.balance_a = swap_pool_state.balance_a
365            .checked_sub(out_amount)
366            .ok_or(InvalidInstructionData)?;
367    }
368
369    if let Some(creator_fee) = &mut swap_pool_state.creator_fee {
370        if is_a_to_b {
371            creator_fee.balance_a = creator_fee.balance_a.checked_add(creator_fee_amount)
372                .ok_or(InvalidInstructionData)?;
373        } else {
374            creator_fee.balance_b = creator_fee.balance_b.checked_add(creator_fee_amount)
375                .ok_or(InvalidInstructionData)?;
376        }
377    }
378    swap_pool_state.pack(&mut swap_pool_state_info.try_borrow_mut_data()?)?;
379
380    Ok(())
381}
382
383fn process_wthdraw(program_id: &Pubkey, accounts: &[AccountInfo], lp_amount: u64, min_a: u64, min_b: u64) -> ProgramResult {
384    let accounts_iter = &mut accounts.iter();
385    let owner_info = next_account_info(accounts_iter)?;
386    let swap_pool_state_info = next_account_info(accounts_iter)?;
387    let source_a_info = next_account_info(accounts_iter)?;
388    let destination_a_info = next_account_info(accounts_iter)?;
389    let source_b_info = next_account_info(accounts_iter)?;
390    let destination_b_info = next_account_info(accounts_iter)?;
391    let lp_mint_info = next_account_info(accounts_iter)?;
392    let source_lp_info = next_account_info(accounts_iter)?;
393
394    let spl_token_program = next_account_info(accounts_iter)?;
395
396    if !owner_info.is_signer {
397        return Err(MissingRequiredSignature);
398    }
399
400    if swap_pool_state_info.owner != program_id {
401        return Err(IllegalOwner);
402    }
403
404    let mut swap_pool_state = SwapPool::unpack(&swap_pool_state_info.try_borrow_data()?)?;
405    if swap_pool_state.token_account_a != *source_a_info.key
406        || swap_pool_state.token_account_b != *source_b_info.key
407        || swap_pool_state.lp_mint != *lp_mint_info.key {
408        return Err(InvalidAccountData);
409    }
410
411    let lp_mint_state = Mint::unpack(&lp_mint_info.try_borrow_data()?)?;
412
413    let (withdraw_a_amount, withdraw_b_amount) = calculate_withdraw_amounts(
414        swap_pool_state.balance_a,
415        swap_pool_state.balance_b,
416        lp_mint_state.supply,
417        lp_amount,
418    ).ok_or(InvalidInstructionData)?;
419
420    if withdraw_a_amount < min_a || withdraw_b_amount < min_b {
421        // todo: add custom error message
422        return Err(InvalidInstructionData);
423    }
424
425    // todo: test burning of more than provided account have
426    let burn_instruction = spl_token::instruction::burn(
427        spl_token_program.key,
428        source_lp_info.key,
429        &swap_pool_state.lp_mint,
430        owner_info.key,
431        &[owner_info.key],
432        lp_amount,
433    )?;
434
435    invoke(
436        &burn_instruction,
437        &[
438            spl_token_program.clone(),
439            source_lp_info.clone(),
440            lp_mint_info.clone(),
441            owner_info.clone(),
442        ],
443    )?;
444
445    let transfer_a_instruction = spl_token::instruction::transfer(
446        spl_token_program.key,
447        &swap_pool_state.token_account_a,
448        destination_a_info.key,
449        swap_pool_state_info.key,
450        &[],
451        withdraw_a_amount,
452    )?;
453    invoke_signed(
454        &transfer_a_instruction,
455        &[
456            spl_token_program.clone(),
457            source_a_info.clone(),
458            destination_a_info.clone(),
459            swap_pool_state_info.clone(),
460        ],
461        &[&[&swap_pool_state.seed]],
462    )?;
463
464    let transfer_b_instruction = spl_token::instruction::transfer(
465        spl_token_program.key,
466        &swap_pool_state.token_account_b,
467        destination_b_info.key,
468        swap_pool_state_info.key,
469        &[],
470        withdraw_b_amount,
471    )?;
472    invoke_signed(
473        &transfer_b_instruction,
474        &[
475            spl_token_program.clone(),
476            source_b_info.clone(),
477            destination_b_info.clone(),
478            swap_pool_state_info.clone(),
479        ],
480        &[&[&swap_pool_state.seed]],
481    )?;
482
483    swap_pool_state.balance_a = swap_pool_state.balance_a
484        .checked_sub(withdraw_a_amount)
485        .ok_or(InvalidInstructionData)?;
486    swap_pool_state.balance_b = swap_pool_state.balance_b
487        .checked_sub(withdraw_b_amount)
488        .ok_or(InvalidInstructionData)?;
489    swap_pool_state.pack(&mut swap_pool_state_info.try_borrow_mut_data()?)?;
490
491    Ok(())
492}