continuation_router/
processor.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Token, TokenAccount};
3use vipers::{assert_keys_eq, invariant};
4
5use crate::{Action, Continuation, SwapActionEvent, TokenAmount};
6
7pub trait ActionInputOutput<'info>: Action {
8    fn input_account(&self) -> &Account<'info, TokenAccount>;
9    fn output_account(&self) -> &Account<'info, TokenAccount>;
10}
11
12pub struct ActionContext<'a, 'b, 'c, 'info, T> {
13    /// Currently executing program id.
14    pub program_id: &'a Pubkey,
15    /// Deserialized accounts.
16    pub action: &'b T,
17    /// Remaining accounts given but not deserialized or validated.
18    /// Be very careful when using this directly.
19    pub remaining_accounts: &'c [AccountInfo<'info>],
20    /// The spl_token program.
21    pub token_program: Program<'info, Token>,
22    /// The relevant swap program.
23    /// CHECK: Checked by executor
24    pub swap_program: AccountInfo<'info>,
25    /// The owner of all involved token accounts.
26    /// CHECK: Arbitrary
27    pub owner: AccountInfo<'info>,
28}
29
30/// Processes a context.
31pub trait Processor<'info>: ActionInputOutput<'info> {
32    fn process_unchecked(&self, amount_in: u64, minimum_amount_out: u64) -> Result<()>;
33
34    fn process(&self, continuation: &mut Account<'info, Continuation>) -> Result<()> {
35        msg!("Router action: {:?}", Self::TYPE);
36        let continuation = continuation;
37        invariant!(continuation.steps_left > 0, NoMoreSteps);
38
39        let input_account = self.input_account();
40        assert_keys_eq!(
41            input_account.key(),
42            continuation.input,
43            PathInputOutputMismatch
44        );
45        assert_keys_eq!(input_account.owner, continuation.owner, InputOwnerMismatch);
46        assert_keys_eq!(
47            input_account.mint,
48            continuation.amount_in.mint,
49            InputMintMismatch
50        );
51
52        // ensure swap is non-zero
53        let amount_in = continuation.amount_in;
54        invariant!(amount_in.amount != 0, ZeroSwap);
55
56        // ensure amount in is at least the desired amount
57        invariant!(
58            input_account.amount >= amount_in.amount,
59            InsufficientInputBalance
60        );
61
62        // ensure output account is owned by the owner
63        let output_account = self.output_account();
64        assert_keys_eq!(
65            output_account.owner,
66            continuation.owner,
67            OutputOwnerMismatch
68        );
69
70        // process step
71        let initial_balance = output_account.amount;
72        let minimum_amount_out = if continuation.steps_left == 1 {
73            assert_keys_eq!(
74                continuation.minimum_amount_out.mint,
75                output_account.mint,
76                OutputMintMismatch
77            );
78            continuation.minimum_amount_out.amount
79        } else {
80            0
81        };
82        self.process_unchecked(amount_in.amount, minimum_amount_out)?;
83        let output_account = &mut output_account.clone();
84        output_account.reload()?;
85        let result_balance = output_account.amount;
86
87        // ensure that the new balance is higher than the old balance
88        invariant!(result_balance >= initial_balance, BalanceLower);
89        let next_amount_in = result_balance - initial_balance;
90
91        // write results
92        continuation.input = output_account.key();
93        continuation.amount_in = TokenAmount::new(output_account.mint, next_amount_in);
94        continuation.steps_left -= 1;
95
96        emit!(SwapActionEvent {
97            action_type: Self::TYPE,
98            owner: continuation.owner,
99            input_amount: amount_in,
100            output_account: continuation.input,
101            output_amount: continuation.amount_in,
102        });
103        Ok(())
104    }
105}