Skip to content
Draft
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
25 changes: 24 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ swap = [
"perena-swap",
"solfi-swap",
"solfi_v2-swap",
"goonfi-swap",
"goonfi_v2-swap",
"manifest-swap",
"heaven-swap",
"aldrin-swap",
Expand All @@ -35,6 +37,8 @@ jupiter-deposit = ["dep:beethoven-deposit-jupiter"]
perena-swap = ["dep:beethoven-swap-perena"]
solfi-swap = ["dep:beethoven-swap-solfi"]
solfi_v2-swap = ["dep:beethoven-swap-solfi-v2"]
goonfi-swap = ["dep:beethoven-swap-goonfi"]
goonfi_v2-swap = ["dep:beethoven-swap-goonfi-v2"]
manifest-swap = ["dep:beethoven-swap-manifest"]
heaven-swap = ["dep:beethoven-swap-heaven"]
aldrin-swap = ["dep:beethoven-swap-aldrin"]
Expand All @@ -57,6 +61,8 @@ beethoven-deposit-jupiter = { path = "crates/deposit/jupiter", optional = true }
beethoven-swap-perena = { path = "crates/swap/perena", optional = true }
beethoven-swap-solfi = { path = "crates/swap/solfi", optional = true }
beethoven-swap-solfi-v2 = { path = "crates/swap/solfi-v2", optional = true }
beethoven-swap-goonfi = { path = "crates/swap/goonfi", optional = true }
beethoven-swap-goonfi-v2 = { path = "crates/swap/goonfi-v2", optional = true }
beethoven-swap-manifest = { path = "crates/swap/manifest", optional = true }
beethoven-swap-heaven = { path = "crates/swap/heaven", optional = true }
beethoven-swap-aldrin = { path = "crates/swap/aldrin", optional = true }
Expand All @@ -74,6 +80,8 @@ members = [
"crates/swap/perena",
"crates/swap/solfi",
"crates/swap/solfi-v2",
"crates/swap/goonfi",
"crates/swap/goonfi-v2",
"crates/swap/manifest",
"crates/swap/heaven",
"crates/swap/aldrin",
Expand All @@ -100,7 +108,6 @@ solana-rent = "3.1.0"
solana-sdk-ids = "3.1.0"
solana-signer = "3.0.0"
solana-transaction = "3.0.2"
solana-compute-budget-instruction = "3.1.7"
spl-token-interface = "2"
mollusk-svm = "0.10.1"
mollusk-svm-programs-token = "0.10.1"
Expand Down
14 changes: 14 additions & 0 deletions crates/swap/goonfi-v2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "beethoven-swap-goonfi-v2"
description = "Goonfi V2 DEX swap implementation for Beethoven"
version = "0.0.1"
license = "MIT"
edition = "2021"


[dependencies]
beethoven-core = { path = "../../core" }
solana-account-view = "1.0.0"
solana-address = { version = "2.0.0", features = ["decode"] }
solana-instruction-view = "1.0.0"
solana-program-error = "3.0.0"
166 changes: 166 additions & 0 deletions crates/swap/goonfi-v2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#![no_std]

use {
beethoven_core::Swap,
core::mem::MaybeUninit,
solana_account_view::AccountView,
solana_address::Address,
solana_instruction_view::{
cpi::{invoke_signed, Signer},
InstructionAccount, InstructionView,
},
solana_program_error::{ProgramError, ProgramResult},
};

pub const GOONFI_V2_PROGRAM_ID: Address =
Address::from_str_const("goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE");

const SWAP_DISCRIMINATOR: u8 = 1;

pub struct GoonfiV2;

pub struct GoonfiV2SwapData {
pub is_bid: bool,
pub is_ultra: bool,
}

impl TryFrom<&[u8]> for GoonfiV2SwapData {
type Error = ProgramError;

fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
if data.len() < 2 {
return Err(ProgramError::InvalidInstructionData);
}

Ok(Self {
is_bid: data[0] != 0,
is_ultra: data[1] != 0,
})
}
}

pub struct GoonfiV2SwapAccounts<'info> {
pub goonfi_v2_program: &'info AccountView,
pub payer: &'info AccountView,
pub market: &'info AccountView,
pub base_token_account: &'info AccountView,
pub quote_token_account: &'info AccountView,
pub base_vault: &'info AccountView,
pub quote_vault: &'info AccountView,
pub base_mint: &'info AccountView,
pub quote_mint: &'info AccountView,
pub oracle: &'info AccountView,
pub blacklist: &'info AccountView,
pub instructions_sysvar: &'info AccountView,
pub base_token_program: &'info AccountView,
pub quote_token_program: &'info AccountView,
}

impl<'info> TryFrom<&'info [AccountView]> for GoonfiV2SwapAccounts<'info> {
type Error = ProgramError;

fn try_from(accounts: &'info [AccountView]) -> Result<Self, Self::Error> {
if accounts.len() < 14 {
return Err(ProgramError::NotEnoughAccountKeys);
}

let [goonfi_v2_program, payer, market, base_token_account, quote_token_account, base_vault, quote_vault, base_mint, quote_mint, oracle, blacklist, instructions_sysvar, base_token_program, quote_token_program, ..] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

Ok(Self {
goonfi_v2_program,
payer,
market,
base_token_account,
quote_token_account,
base_vault,
quote_vault,
base_mint,
quote_mint,
oracle,
blacklist,
instructions_sysvar,
base_token_program,
quote_token_program,
})
}
}

impl<'info> Swap<'info> for GoonfiV2 {
type Accounts = GoonfiV2SwapAccounts<'info>;
type Data = GoonfiV2SwapData;

fn swap_signed(
ctx: &Self::Accounts,
in_amount: u64,
minimum_out_amount: u64,
data: &Self::Data,
signer_seeds: &[Signer],
) -> ProgramResult {
let accounts = [
InstructionAccount::readonly_signer(ctx.payer.address()),
InstructionAccount::writable(ctx.market.address()),
InstructionAccount::writable(ctx.base_token_account.address()),
InstructionAccount::writable(ctx.quote_token_account.address()),
InstructionAccount::writable(ctx.base_vault.address()),
InstructionAccount::writable(ctx.quote_vault.address()),
InstructionAccount::readonly(ctx.base_mint.address()),
InstructionAccount::readonly(ctx.quote_mint.address()),
InstructionAccount::readonly(ctx.oracle.address()),
InstructionAccount::readonly(ctx.blacklist.address()),
InstructionAccount::readonly(ctx.instructions_sysvar.address()),
InstructionAccount::readonly(ctx.base_token_program.address()),
InstructionAccount::readonly(ctx.quote_token_program.address()),
];

let account_infos = [
ctx.payer,
ctx.market,
ctx.base_token_account,
ctx.quote_token_account,
ctx.base_vault,
ctx.quote_vault,
ctx.base_mint,
ctx.quote_mint,
ctx.oracle,
ctx.blacklist,
ctx.instructions_sysvar,
ctx.base_token_program,
ctx.quote_token_program,
];

let mut instruction_data = MaybeUninit::<[u8; 19]>::uninit();
unsafe {
let ptr = instruction_data.as_mut_ptr() as *mut u8;
core::ptr::write(ptr, SWAP_DISCRIMINATOR);
core::ptr::write(ptr.add(1), data.is_bid as u8);
core::ptr::copy_nonoverlapping(in_amount.to_le_bytes().as_ptr(), ptr.add(2), 8);
core::ptr::copy_nonoverlapping(
minimum_out_amount.to_le_bytes().as_ptr(),
ptr.add(10),
8,
);
core::ptr::write(ptr.add(18), data.is_ultra as u8);
}

let instruction = InstructionView {
program_id: &GOONFI_V2_PROGRAM_ID,
accounts: &accounts,
data: unsafe { instruction_data.assume_init_ref() },
};

invoke_signed(&instruction, &account_infos, signer_seeds)
}

fn swap(
ctx: &Self::Accounts,
in_amount: u64,
minimum_out_amount: u64,
data: &Self::Data,
) -> ProgramResult {
Self::swap_signed(ctx, in_amount, minimum_out_amount, data, &[])
}
}
14 changes: 14 additions & 0 deletions crates/swap/goonfi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "beethoven-swap-goonfi"
description = "Goonfi DEX swap implementation for Beethoven"
version = "0.0.1"
license = "MIT"
edition = "2021"


[dependencies]
beethoven-core = { path = "../../core" }
solana-account-view = "1.0.0"
solana-address = { version = "2.0.0", features = ["decode"] }
solana-instruction-view = "1.0.0"
solana-program-error = "3.0.0"
Loading