diff --git a/README.md b/README.md index 5743545..67eb0a7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ List of supported programs: - [x] BisonFi - [x] HumidiFi - [x] Obric +- [x] Scorch - [x] SolFi - [x] Tessera - [x] ZeroFi @@ -52,6 +53,7 @@ CU benchmarks between Mona, Jupiter and direct (non-router) program hit: | goonfi | — | — | 87,703 | | humidifi | 41,194 | 47,342 | 52,723 | | obric | 54,406 | 56,813 | 61,111 | +| scorch | TODO | TODO | TODO | | solfi | 82,616 | 90,086 | 99,717 | | tessera | — | 63,198 | 79,376 | | zerofi | 34,734 | 37,238 | 41,910 | diff --git a/src/adapters.rs b/src/adapters.rs index d5d85e2..b498b3e 100644 --- a/src/adapters.rs +++ b/src/adapters.rs @@ -3,6 +3,7 @@ pub mod aquifer_v1; pub mod bisonfi_v1; pub mod humidifi_v1; pub mod obric_v2; +pub mod scorch_v1; pub mod solfi_v2; pub mod tessera_v1; pub mod zerofi_v1; diff --git a/src/adapters/scorch_v1.rs b/src/adapters/scorch_v1.rs new file mode 100644 index 0000000..ca591a0 --- /dev/null +++ b/src/adapters/scorch_v1.rs @@ -0,0 +1,105 @@ +use crate::cons::scorch::{ACCS_LEN, ARGS_LEN, SWAP_SELECTOR}; +use pinocchio::cpi::{invoke_unchecked, CpiAccount}; +use pinocchio::instruction::{InstructionAccount, InstructionView}; +use pinocchio::AccountView; + +// Scorch V1 swap args: selector(1) + param(17) + amount_in(8) + min_out(8) = 34 bytes. +#[repr(C, packed)] +pub struct SwapArgs { + pub selector: [u8; 1], + pub scorch_param: [u8; 17], + pub amount_in: [u8; 8], + pub min_out: [u8; 8], +} + +impl SwapArgs { + pub fn new(amount_in: u64) -> Self { + Self { + selector: *SWAP_SELECTOR, + scorch_param: [0u8; 17], // patched by caller from scorch_param key + amount_in: amount_in.to_le_bytes(), + min_out: 1u64.to_le_bytes(), + } + } + + pub fn as_bytes(&self) -> &[u8; ARGS_LEN] { + unsafe { &*(self as *const Self as *const [u8; ARGS_LEN]) } + } +} + +/// Remaining layout (18 accounts): +/// 0 program (readonly) +/// 1 market (readonly) +/// 2 user_ata_a (writable) +/// 3 user_ata_b (writable) +/// 4 market_ta_a (writable) +/// 5 market_ta_b (writable) +/// 6 mint_a (readonly) +/// 7 mint_b (readonly) +/// 8 token_prog (readonly) +/// 9 token_prog (readonly) +/// 10 memo_prog (readonly) +/// 11 core_prog (readonly) +/// 12 acc1 (readonly) +/// 13 state_a (writable) +/// 14 state_b (writable) +/// 15 state_c (writable) +/// 16 sysvar_ixs (readonly) +/// 17 scorch_param (readonly) - encoded in first 16 bytes of key +/// +/// CPI to Scorch (18 accounts): payer injected at position 1. +pub fn swap_v1(payer: &AccountView, rem: &[AccountView], amount_in: u64, _a_to_b: bool) { + let mut args = SwapArgs::new(amount_in); + + // extract params from the scorch_param account key (first 17 bytes) + args.scorch_param + .copy_from_slice(&rem[17].address().as_ref()[..17]); + + let ix_accs = [ + InstructionAccount::readonly(rem[1].address()), // market + InstructionAccount::writable_signer(payer.address()), // payer + InstructionAccount::writable(rem[2].address()), // user_ata_a + InstructionAccount::writable(rem[3].address()), // user_ata_b + InstructionAccount::writable(rem[4].address()), // market_ta_a + InstructionAccount::writable(rem[5].address()), // market_ta_b + InstructionAccount::readonly(rem[6].address()), // mint_a + InstructionAccount::readonly(rem[7].address()), // mint_b + InstructionAccount::readonly(rem[8].address()), // token_prog + InstructionAccount::readonly(rem[9].address()), // token_prog + InstructionAccount::readonly(rem[10].address()), // memo_prog + InstructionAccount::readonly(rem[11].address()), // core_prog + InstructionAccount::readonly(rem[12].address()), // acc1 + InstructionAccount::writable(rem[13].address()), // state_a + InstructionAccount::writable(rem[14].address()), // state_b + InstructionAccount::writable(rem[15].address()), // state_c + InstructionAccount::readonly(rem[16].address()), // sysvar_ixs + ]; + + let ix = InstructionView { + program_id: rem[0].address(), + data: args.as_bytes(), + accounts: &ix_accs, + }; + + let cpi: [CpiAccount; ACCS_LEN - 1] = [ + CpiAccount::from(&rem[1]), + CpiAccount::from(payer), + CpiAccount::from(&rem[2]), + CpiAccount::from(&rem[3]), + CpiAccount::from(&rem[4]), + CpiAccount::from(&rem[5]), + CpiAccount::from(&rem[6]), + CpiAccount::from(&rem[7]), + CpiAccount::from(&rem[8]), + CpiAccount::from(&rem[9]), + CpiAccount::from(&rem[10]), + CpiAccount::from(&rem[11]), + CpiAccount::from(&rem[12]), + CpiAccount::from(&rem[13]), + CpiAccount::from(&rem[14]), + CpiAccount::from(&rem[15]), + CpiAccount::from(&rem[16]), + ]; + + unsafe { invoke_unchecked(&ix, &cpi) } +} diff --git a/src/cons.rs b/src/cons.rs index e15d864..2fdd0b0 100644 --- a/src/cons.rs +++ b/src/cons.rs @@ -2,8 +2,10 @@ use five8_const::decode_32_const as d; pub const MAX_HOPS: usize = 30; -const OBF_CPI_KEY_SEED: [u8; 32] = - [58, 255, 47, 255, 226, 186, 235, 195, 123, 131, 245, 8, 11, 233, 132, 219, 225, 40, 79, 119, 169, 121, 169, 58, 197, 1, 122, 9, 216, 164, 149, 97]; +const OBF_CPI_KEY_SEED: [u8; 32] = [ + 58, 255, 47, 255, 226, 186, 235, 195, 123, 131, 245, 8, 11, 233, 132, 219, 225, 40, 79, 119, + 169, 121, 169, 58, 197, 1, 122, 9, 216, 164, 149, 97, +]; pub const OBF_CPI_KEY: u64 = u64::from_le_bytes([ OBF_CPI_KEY_SEED[0], OBF_CPI_KEY_SEED[1], @@ -74,6 +76,13 @@ pub mod alphaq { pub const ARGS_LEN: usize = 18; } +pub mod scorch { + pub const ID: [u8; 32] = super::d("SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn"); + pub const SWAP_SELECTOR: &[u8; 1] = &[0x02]; + pub const ACCS_LEN: usize = 18; + pub const ARGS_LEN: usize = 34; +} + /// DEX discriminant; /// each variant maps to a specific adapter. #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -85,13 +94,25 @@ pub enum Dex { HumidifiV2 = 3, HumidifiV3 = 4, Obric = 5, - Solfi = 6, - Tessera = 7, - Zerofi = 8, + Scorch = 6, + Solfi = 7, + Tessera = 8, + Zerofi = 9, } impl Dex { - pub const ALL: [Dex; 9] = [Dex::Alphaq, Dex::Aquifer, Dex::Bisonfi, Dex::HumidifiV2, Dex::HumidifiV3, Dex::Obric, Dex::Solfi, Dex::Tessera, Dex::Zerofi]; + pub const ALL: [Dex; 10] = [ + Dex::Alphaq, + Dex::Aquifer, + Dex::Bisonfi, + Dex::HumidifiV2, + Dex::HumidifiV3, + Dex::Obric, + Dex::Scorch, + Dex::Solfi, + Dex::Tessera, + Dex::Zerofi, + ]; /// Number of remaining accounts per hop for swap_v1 (excludes shared payer). #[inline(always)] @@ -108,6 +129,7 @@ impl Dex { Dex::Bisonfi => if a_to_b { 5 } else { 4 }, Dex::HumidifiV2 | Dex::HumidifiV3 => if a_to_b { 5 } else { 4 }, Dex::Obric => if a_to_b { 7 } else { 6 }, + Dex::Scorch => 3, Dex::Solfi => if a_to_b { 7 } else { 6 }, Dex::Tessera => if a_to_b { 6 } else { 5 }, Dex::Zerofi => 7, @@ -117,7 +139,7 @@ impl Dex { /// Map byte to Dex variant. #[inline(always)] pub fn from_u8(v: u8) -> Option { - if v <= 8 { + if v <= 9 { Some(Self::ALL[v as usize]) } else { None @@ -125,14 +147,15 @@ impl Dex { } } -const REM_ACCS_LEN_V1: [usize; 9] = [ +const REM_ACCS_LEN_V1: [usize; 10] = [ alphaq::ACCS_LEN, // 0 Alphaq aquifer::ACCS_LEN, // 1 Aquifer bisonfi::ACCS_LEN, // 2 Bisonfi humidifi::ACCS_LEN_V2V3, // 3 HumidifiV2 humidifi::ACCS_LEN_V2V3, // 4 HumidifiV3 obric::ACCS_LEN, // 5 Obric - solfi::ACCS_LEN, // 6 Solfi - tessera::ACCS_LEN, // 7 Tessera - zerofi::ACCS_LEN, // 8 Zerofi + scorch::ACCS_LEN, // 6 Scorch + solfi::ACCS_LEN, // 7 Solfi + tessera::ACCS_LEN, // 8 Tessera + zerofi::ACCS_LEN, // 9 Zerofi ]; diff --git a/src/ixs/swap_v1.rs b/src/ixs/swap_v1.rs index 08721c2..032d5a2 100644 --- a/src/ixs/swap_v1.rs +++ b/src/ixs/swap_v1.rs @@ -100,6 +100,7 @@ fn dispatch(payer: &AccountView, rem: &[AccountView], amount_in: u64, a_to_b: bo Dex::Bisonfi => adapters::bisonfi_v1::swap_v1, Dex::HumidifiV2 | Dex::HumidifiV3 => adapters::humidifi_v1::swap_v3, Dex::Obric => adapters::obric_v2::swap_v1, + Dex::Scorch => adapters::scorch_v1::swap_v1, Dex::Solfi => adapters::solfi_v2::swap_v1, Dex::Tessera => adapters::tessera_v1::swap_v1, Dex::Zerofi => adapters::zerofi_v1::swap_v1,