use continuation_router_syn::router_action;
use anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES};
use anchor_spl::token::{Token, TokenAccount};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use vipers::prelude::*;
pub mod action;
pub mod processor;
use crate::action::ProcessAction;
use crate::processor::{ActionContext, Processor};
declare_id!("Crt7UoUR6QgrFrN7j8rmSQpUTNWNSitSwWvsWGf1qZ5t");
macro_rules! process_action {
($ctx:expr) => {{
let ctx = $ctx;
let cont = &mut ctx.accounts.continuation.continuation;
let action = &ctx.accounts.action;
let action_ctx = &ActionContext {
program_id: ctx.program_id,
action,
remaining_accounts: ctx.remaining_accounts,
token_program: ctx.accounts.continuation.token_program.clone(),
swap_program: ctx.accounts.continuation.swap_program.to_account_info(),
owner: ctx.accounts.continuation.owner.to_account_info(),
};
Processor::process(action_ctx, cont)
}};
}
#[program]
pub mod continuation_router {
use super::*;
pub fn create_ata_if_not_exists(ctx: Context<CreateATAIfNotExists>) -> Result<()> {
if !ctx.accounts.ata.try_borrow_data()?.is_empty() {
return Ok(());
}
anchor_spl::associated_token::create(CpiContext::new(
ctx.accounts.associated_token_program.to_account_info(),
anchor_spl::associated_token::Create {
payer: ctx.accounts.payer.to_account_info(),
associated_token: ctx.accounts.ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
},
))?;
Ok(())
}
pub fn begin(
ctx: Context<Begin>,
amount_in: u64,
minimum_amount_out: u64,
num_steps: u16,
) -> Result<()> {
let continuation = &mut ctx.accounts.continuation;
continuation.owner = *ctx.accounts.owner.key;
continuation.payer = *ctx.accounts.payer.key;
continuation.input = *ctx.accounts.input.to_account_info().key;
continuation.initial_amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
continuation.output = *ctx.accounts.output.to_account_info().key;
continuation.output_initial_balance = ctx.accounts.output.amount;
continuation.amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
continuation.minimum_amount_out =
TokenAmount::new(ctx.accounts.output.mint, minimum_amount_out);
continuation.steps_left = num_steps;
continuation.__nonce = *unwrap_int!(ctx.bumps.get("continuation"));
Ok(())
}
pub fn begin_v2(
ctx: Context<BeginV2>,
amount_in: u64,
minimum_amount_out: u64,
num_steps: u16,
) -> Result<()> {
let continuation = &mut ctx.accounts.continuation;
continuation.owner = ctx.accounts.owner.key();
continuation.payer = ctx.accounts.owner.key();
continuation.input = ctx.accounts.input.key();
continuation.initial_amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
continuation.output = ctx.accounts.output.key();
continuation.output_initial_balance = ctx.accounts.output.amount;
continuation.amount_in = TokenAmount::new(ctx.accounts.input.mint, amount_in);
continuation.minimum_amount_out =
TokenAmount::new(ctx.accounts.output.mint, minimum_amount_out);
continuation.steps_left = num_steps;
Ok(())
}
pub fn end(ctx: Context<End>) -> Result<()> {
let continuation = &ctx.accounts.continuation;
require!(continuation.steps_left == 0, EndIncomplete);
let result_balance = ctx.accounts.output.amount;
require!(
result_balance >= continuation.output_initial_balance,
BalanceLower
);
require!(
ctx.accounts.output.mint == continuation.minimum_amount_out.mint,
OutputMintMismatch,
);
let mut amount_out = result_balance - continuation.output_initial_balance;
if continuation.initial_amount_in.mint == ctx.accounts.output.mint {
amount_out += continuation.initial_amount_in.amount;
}
require!(
amount_out >= continuation.minimum_amount_out.amount,
MinimumOutNotMet,
);
emit!(SwapCompleteEvent {
owner: continuation.owner,
amount_in: continuation.initial_amount_in,
amount_out: TokenAmount::new(continuation.minimum_amount_out.mint, amount_out),
});
Ok(())
}
pub fn ss_swap<'info>(ctx: Context<'_, '_, '_, 'info, SSSwapAccounts<'info>>) -> Result<()> {
process_action!(ctx)
}
pub fn ss_withdraw_one<'info>(
ctx: Context<'_, '_, '_, 'info, SSWithdrawOneAccounts<'info>>,
) -> Result<()> {
process_action!(ctx)
}
pub fn ss_deposit_a<'info>(
ctx: Context<'_, '_, '_, 'info, SSDepositAAccounts<'info>>,
) -> Result<()> {
process_action!(ctx)
}
pub fn ss_deposit_b<'info>(
ctx: Context<'_, '_, '_, 'info, SSDepositBAccounts<'info>>,
) -> Result<()> {
process_action!(ctx)
}
pub fn ad_withdraw<'info>(
ctx: Context<'_, '_, '_, 'info, ADWithdrawAccounts<'info>>,
) -> Result<()> {
process_action!(ctx)
}
pub fn ad_deposit<'info>(
ctx: Context<'_, '_, '_, 'info, ADDepositAccounts<'info>>,
) -> Result<()> {
process_action!(ctx)
}
}
#[router_action]
#[derive(Accounts)]
pub struct SSSwap<'info> {
pub swap: StableSwap<'info>,
pub input: SwapToken<'info>,
pub output: SwapOutput<'info>,
}
#[router_action]
#[derive(Accounts)]
pub struct SSWithdrawOne<'info> {
pub swap: StableSwap<'info>,
#[account(mut)]
pub pool_mint: AccountInfo<'info>,
#[account(mut)]
pub input_lp: Account<'info, TokenAccount>,
#[account(mut)]
pub quote_reserves: AccountInfo<'info>,
pub output: SwapOutput<'info>,
}
#[router_action]
#[derive(Accounts)]
pub struct SSDepositA<'info> {
pub inner: SSDeposit<'info>,
}
#[router_action]
#[derive(Accounts)]
pub struct SSDepositB<'info> {
pub inner: SSDeposit<'info>,
}
#[router_action(pass_through)]
#[derive(Accounts)]
pub struct ADWithdraw<'info> {
pub input: Account<'info, TokenAccount>,
pub output: Account<'info, TokenAccount>,
}
#[router_action(pass_through)]
#[derive(Accounts)]
pub struct ADDeposit<'info> {
pub input: Account<'info, TokenAccount>,
pub output: Account<'info, TokenAccount>,
}
#[derive(Accounts)]
pub struct CreateATAIfNotExists<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub ata: SystemAccount<'info>,
pub authority: UncheckedAccount<'info>,
pub mint: UncheckedAccount<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, anchor_spl::associated_token::AssociatedToken>,
}
#[derive(Accounts)]
pub struct Begin<'info> {
#[account(
init,
seeds = [
b"anchor".as_ref(),
owner.key().as_ref(),
random.key().as_ref()
],
bump,
space = 8 + Continuation::LEN,
payer = payer
)]
pub continuation: Box<Account<'info, Continuation>>,
pub random: UncheckedAccount<'info>,
#[account(has_one = owner)]
pub input: Box<Account<'info, TokenAccount>>,
#[account(has_one = owner)]
pub output: Box<Account<'info, TokenAccount>>,
pub owner: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct BeginV2<'info> {
#[account(zero)]
pub continuation: Box<Account<'info, Continuation>>,
#[account(has_one = owner)]
pub input: Box<Account<'info, TokenAccount>>,
#[account(has_one = owner)]
pub output: Box<Account<'info, TokenAccount>>,
pub owner: Signer<'info>,
}
#[derive(Accounts)]
pub struct End<'info> {
#[account(
mut,
close = payer,
has_one = owner,
has_one = payer,
has_one = output,
)]
pub continuation: Box<Account<'info, Continuation>>,
pub output: Box<Account<'info, TokenAccount>>,
pub owner: Signer<'info>,
#[account(mut)]
pub payer: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct SSSwapAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: SSSwap<'info>,
}
#[derive(Accounts)]
pub struct SSWithdrawOneAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: SSWithdrawOne<'info>,
}
#[derive(Accounts)]
pub struct SSDepositAAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: SSDepositA<'info>,
}
#[derive(Accounts)]
pub struct SSDepositBAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: SSDepositB<'info>,
}
#[derive(Accounts)]
pub struct ADWithdrawAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: ADWithdraw<'info>,
}
#[derive(Accounts)]
pub struct ADDepositAccounts<'info> {
pub continuation: ContinuationAccounts<'info>,
pub action: ADDeposit<'info>,
}
#[derive(Accounts)]
pub struct ContinuationAccounts<'info> {
#[account(
mut,
has_one = owner,
)]
pub continuation: Box<Account<'info, Continuation>>,
pub token_program: Program<'info, Token>,
pub swap_program: UncheckedAccount<'info>,
pub owner: Signer<'info>,
}
#[derive(Accounts)]
pub struct SSDeposit<'info> {
pub swap: StableSwap<'info>,
pub input_a: SwapToken<'info>,
pub input_b: SwapToken<'info>,
#[account(mut)]
pub pool_mint: AccountInfo<'info>,
#[account(mut)]
pub output_lp: Account<'info, TokenAccount>,
}
#[derive(Accounts)]
pub struct StableSwap<'info> {
pub swap: AccountInfo<'info>,
pub swap_authority: AccountInfo<'info>,
pub clock: Sysvar<'info, Clock>,
}
#[derive(Accounts)]
pub struct SwapToken<'info> {
#[account(mut)]
pub user: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub reserve: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct SwapOutput<'info> {
pub user_token: SwapToken<'info>,
#[account(mut)]
pub fees: AccountInfo<'info>,
}
#[account]
#[derive(Default)]
pub struct Continuation {
pub owner: Pubkey,
pub payer: Pubkey,
pub initial_amount_in: TokenAmount,
pub input: Pubkey,
pub amount_in: TokenAmount,
pub steps_left: u16,
pub output: Pubkey,
pub output_initial_balance: u64,
pub minimum_amount_out: TokenAmount,
__nonce: u8,
}
impl Continuation {
pub const LEN: usize = PUBKEY_BYTES * 2
+ TokenAmount::LEN
+ PUBKEY_BYTES
+ TokenAmount::LEN
+ 2
+ PUBKEY_BYTES
+ 8
+ TokenAmount::LEN
+ 1;
}
#[error_code]
pub enum ErrorCode {
#[msg("Path input does not match prior output.")]
PathInputOutputMismatch,
#[msg("Error in a transitive swap input/output calculation.")]
TransitiveSwapCalculationError,
#[msg("Swap result overflowed when checking balance difference.")]
OverflowSwapResult,
#[msg("Swap resulted in a balance lower than the original balance.")]
BalanceLower,
#[msg("Cannot perform a zero swap.")]
ZeroSwap,
#[msg("Input owner does not match continuation owner.")]
InputOwnerMismatch,
#[msg("Input mint does not match continuation input mint.")]
InputMintMismatch,
#[msg("Output owner does not match continuation owner.")]
OutputOwnerMismatch,
#[msg("No more steps to process.")]
NoMoreSteps,
#[msg("Insufficient input balance")]
InsufficientInputBalance,
#[msg("Not all steps were processed.")]
EndIncomplete,
#[msg("Minimum amount out not met.")]
MinimumOutNotMet,
#[msg("Output mint does not match continuation output mint.")]
OutputMintMismatch,
}
#[event]
pub struct SwapActionEvent {
pub action_type: ActionType,
pub owner: Pubkey,
pub input_amount: TokenAmount,
pub output_account: Pubkey,
pub output_amount: TokenAmount,
}
#[event]
pub struct SwapCompleteEvent {
pub owner: Pubkey,
pub amount_in: TokenAmount,
pub amount_out: TokenAmount,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TokenAmount {
pub mint: Pubkey,
pub amount: u64,
}
impl TokenAmount {
pub const LEN: usize = PUBKEY_BYTES + 8;
fn new(mint: Pubkey, amount: u64) -> TokenAmount {
TokenAmount { mint, amount }
}
}
pub trait Action {
const TYPE: ActionType;
}
#[interface]
pub trait RouterActionProcessor<'info, T: Accounts<'info>> {
fn process_action(
ctx: Context<T>,
action: u16,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<()>;
}
#[derive(
AnchorSerialize, AnchorDeserialize, IntoPrimitive, TryFromPrimitive, Copy, Clone, Debug,
)]
#[repr(u16)]
pub enum ActionType {
SSSwap = 0,
SSWithdrawOne = 1,
SSDepositA = 2,
SSDepositB = 3,
ADWithdraw = 10,
ADDeposit = 11,
}