Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,884 changes: 8,884 additions & 0 deletions pinocchio/swap/Cargo.lock

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions pinocchio/swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[package]
name = "pinocchio-swap"
version = "0.1.0"
description = "Pinocchio-based AMM swap program using Light Protocol rent-free accounts"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "pinocchio_swap"

[features]
no-entrypoint = []
default = []
test-sbf = []

[dependencies]
# Main Light Protocol dependencies
light-account-pinocchio = { version = "0.20", features = ["token", "std"] }
light-token-pinocchio = "0.20"
light-hasher = { version = "5.0.0", features = ["solana"] }

# Serialization and utilities
borsh = { version = "0.10.4", default-features = false }
bytemuck = { version = "1.21", features = ["derive"] }

# Pinocchio and Solana
pinocchio = "0.9"
pinocchio-pubkey = { version = "0.3", features = ["const"] }
pinocchio-system = "0.3"
solana-pubkey = "2.2"
solana-instruction = "2.2"
solana-msg = "2.2"
solana-program-error = "2.2"
Comment on lines +30 to +33
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not be necessary


[dev-dependencies]
light-program-test = "0.20"
light-client = { version = "0.20", features = ["v2", "anchor", "program-test"] }
light-token = { version = "0.20", features = ["anchor"] }
light-token-client = "0.20"
light-account = { version = "0.20", features = ["token"] }
tokio = { version = "1", features = ["full"] }
solana-sdk = "2.2"
solana-account = "2.2"
solana-keypair = "2.2"
solana-signer = "2.2"
blake3 = "=1.8.2"

[lints.rust.unexpected_cfgs]
level = "allow"
check-cfg = [
'cfg(target_os, values("solana"))',
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
]
35 changes: 35 additions & 0 deletions pinocchio/swap/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Constants for the swap program including seeds and discriminators.

/// Seed for pool state PDA.
pub const POOL_SEED: &[u8] = b"pool";

/// Seed for global pool authority PDA (controls all vaults).
/// Using a single global authority (like cp-swap) enables vault decompression.
pub const POOL_AUTHORITY_SEED: &[u8] = b"pool_authority";

/// Seed for pool vault token accounts.
pub const POOL_VAULT_SEED: &[u8] = b"pool_vault";

/// Seed for mint A.
pub const MINT_A_SEED: &[u8] = b"mint_a";

/// Seed for mint B.
pub const MINT_B_SEED: &[u8] = b"mint_b";

/// Seed for user token accounts.
pub const USER_TOKEN_SEED: &[u8] = b"user_token";

/// Instruction discriminators (Anchor-compatible: sha256("global:{name}")[..8]).
pub mod discriminators {
/// Initialize pool instruction.
pub const INITIALIZE: [u8; 8] = [175, 175, 109, 31, 13, 152, 155, 237];

/// Swap instruction.
pub const SWAP: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200];
}

/// Default fee in basis points (0.3%).
pub const DEFAULT_FEE_BPS: u16 = 30;

/// Mint decimals for tokens.
pub const MINT_DECIMALS: u8 = 9;
47 changes: 47 additions & 0 deletions pinocchio/swap/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Custom errors for the swap program.

use pinocchio::program_error::ProgramError;

/// Swap program errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SwapError {
/// Invalid pool seeds.
InvalidPoolSeeds = 6000,
/// Invalid authority seeds.
InvalidAuthoritySeeds = 6001,
/// Invalid vault seeds.
InvalidVaultSeeds = 6002,
/// Invalid mint seeds.
InvalidMintSeeds = 6003,
/// Slippage tolerance exceeded.
SlippageExceeded = 6004,
/// Insufficient liquidity for swap.
InsufficientLiquidity = 6005,
/// Invalid token account owner.
InvalidTokenOwner = 6006,
/// Invalid pool state.
InvalidPoolState = 6007,
/// Math overflow.
MathOverflow = 6008,
/// Zero amount not allowed.
ZeroAmount = 6009,
/// Invalid mint for pool.
InvalidMint = 6010,
/// Account not writable.
AccountNotWritable = 6011,
/// Missing signer.
MissingSigner = 6012,
}

impl From<SwapError> for ProgramError {
fn from(e: SwapError) -> Self {
ProgramError::Custom(e as u32)
}
}

impl From<SwapError> for u32 {
fn from(e: SwapError) -> Self {
e as u32
}
}
233 changes: 233 additions & 0 deletions pinocchio/swap/src/init/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
//! Initialize instruction account parsing.

use borsh::{BorshDeserialize, BorshSerialize};
use light_account_pinocchio::{CreateAccountsProof, LightAccount, LightDiscriminator};
use pinocchio::{
account_info::AccountInfo,
instruction::{Seed, Signer},
program_error::ProgramError,
sysvars::Sysvar,
};

use crate::constants::*;
use crate::state::PoolState;

/// Parameters for the initialize instruction.
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct InitializeParams {
/// Proof data for creating compressed accounts.
pub create_accounts_proof: CreateAccountsProof,
/// Fee in basis points (e.g., 30 = 0.3%).
pub fee_bps: u16,
/// Mint signer A bump.
pub mint_signer_a_bump: u8,
/// Mint signer B bump.
pub mint_signer_b_bump: u8,
/// Pool bump.
pub pool_bump: u8,
/// Vault A bump.
pub vault_a_bump: u8,
/// Vault B bump.
pub vault_b_bump: u8,
}

/// Accounts for the initialize instruction.
pub struct InitializeAccounts<'a> {
/// Payer/creator of the pool (signer).
pub payer: &'a AccountInfo,
/// Authority for mints (signer).
pub authority: &'a AccountInfo,
/// Mint signers slice (mint_signer_a, mint_signer_b).
pub mint_signers: &'a [AccountInfo],
/// Mints slice (mint_a, mint_b).
pub mints: &'a [AccountInfo],
/// Pool state account (PDA).
pub pool: &'a AccountInfo,
/// Pool authority PDA.
pub pool_authority: &'a AccountInfo,
/// Vaults slice (vault_a, vault_b).
pub vaults: &'a [AccountInfo],
/// Compressible config account (swap program's config for pool PDA).
pub compressible_config: &'a AccountInfo,
/// Rent sponsor for pool PDA.
pub rent_sponsor: &'a AccountInfo,
/// Light token config (LIGHT_TOKEN_CONFIG for mint creation).
pub light_token_config: &'a AccountInfo,
/// Light token rent sponsor (for mint creation).
pub light_token_rent_sponsor: &'a AccountInfo,
/// Light token program.
pub light_token_program: &'a AccountInfo,
/// CPI authority.
pub cpi_authority: &'a AccountInfo,
/// System program.
pub system_program: &'a AccountInfo,
}

impl<'a> InitializeAccounts<'a> {
/// Number of fixed accounts for this instruction.
/// payer, authority, mint_signer_a, mint_signer_b, mint_a, mint_b,
/// pool, pool_authority, vault_a, vault_b,
/// compressible_config, rent_sponsor, light_token_config, light_token_rent_sponsor,
/// light_token_program, cpi_authority, system_program
pub const FIXED_LEN: usize = 17;

/// Parse accounts from the account info slice.
pub fn parse(
accounts: &'a [AccountInfo],
params: &InitializeParams,
) -> Result<Self, ProgramError> {
if accounts.len() < Self::FIXED_LEN {
return Err(ProgramError::NotEnoughAccountKeys);
}

let payer = &accounts[0];
let authority = &accounts[1];
let mint_signers = &accounts[2..4]; // mint_signer_a, mint_signer_b
let mints = &accounts[4..6]; // mint_a, mint_b
let pool = &accounts[6];
let pool_authority = &accounts[7];
let vaults = &accounts[8..10]; // vault_a, vault_b
let compressible_config = &accounts[10];
let rent_sponsor = &accounts[11];
let light_token_config = &accounts[12];
let light_token_rent_sponsor = &accounts[13];
let light_token_program = &accounts[14];
let cpi_authority = &accounts[15];
let system_program = &accounts[16];

// Validate signers
if !payer.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}
if !authority.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}

// Validate mint_signer_a PDA
{
let authority_key = authority.key();
let seeds: &[&[u8]] = &[MINT_A_SEED, authority_key];
let (expected_pda, expected_bump) =
pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if mint_signers[0].key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
if expected_bump != params.mint_signer_a_bump {
return Err(ProgramError::InvalidSeeds);
}
}

// Validate mint_signer_b PDA
{
let authority_key = authority.key();
let seeds: &[&[u8]] = &[MINT_B_SEED, authority_key];
let (expected_pda, expected_bump) =
pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if mint_signers[1].key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
if expected_bump != params.mint_signer_b_bump {
return Err(ProgramError::InvalidSeeds);
}
}

// Validate and create pool PDA
{
let mint_a_key = mints[0].key();
let mint_b_key = mints[1].key();
let seeds: &[&[u8]] = &[POOL_SEED, mint_a_key.as_ref(), mint_b_key.as_ref()];
let (expected_pda, bump) = pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if pool.key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
if bump != params.pool_bump {
return Err(ProgramError::InvalidSeeds);
}

// Create pool account
let space = 8 + PoolState::INIT_SPACE;
let rent = pinocchio::sysvars::rent::Rent::get()
.map_err(|_| ProgramError::UnsupportedSysvar)?;
let lamports = rent.minimum_balance(space);

let bump_bytes = [bump];
let seed_array = [
Seed::from(POOL_SEED),
Seed::from(mint_a_key.as_ref()),
Seed::from(mint_b_key.as_ref()),
Seed::from(bump_bytes.as_ref()),
];
let signer = Signer::from(&seed_array);
pinocchio_system::instructions::CreateAccount {
from: payer,
to: pool,
lamports,
space: space as u64,
owner: &crate::ID,
}
.invoke_signed(&[signer])?;

// Write discriminator to first 8 bytes
let mut data = pool
.try_borrow_mut_data()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
data[..8].copy_from_slice(&PoolState::LIGHT_DISCRIMINATOR);
}

// Validate global pool authority PDA
{
let seeds: &[&[u8]] = &[POOL_AUTHORITY_SEED];
let (expected_pda, _) = pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if pool_authority.key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
}

Ok(Self {
payer,
authority,
mint_signers,
mints,
pool,
pool_authority,
vaults,
compressible_config,
rent_sponsor,
light_token_config,
light_token_rent_sponsor,
light_token_program,
cpi_authority,
system_program,
})
}

/// Get mint A account.
pub fn mint_a(&self) -> &AccountInfo {
&self.mints[0]
}

/// Get mint B account.
pub fn mint_b(&self) -> &AccountInfo {
&self.mints[1]
}

/// Get vault A account.
pub fn vault_a(&self) -> &AccountInfo {
&self.vaults[0]
}

/// Get vault B account.
pub fn vault_b(&self) -> &AccountInfo {
&self.vaults[1]
}

/// Get mint signer A account.
pub fn mint_signer_a(&self) -> &AccountInfo {
&self.mint_signers[0]
}

/// Get mint signer B account.
pub fn mint_signer_b(&self) -> &AccountInfo {
&self.mint_signers[1]
}
}
6 changes: 6 additions & 0 deletions pinocchio/swap/src/init/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Initialize instruction module.

pub mod accounts;
pub mod processor;

pub use accounts::InitializeParams;
Loading
Loading