diff --git a/Cargo.lock b/Cargo.lock index 58d1dff..954bac6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,9 +941,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -1864,6 +1864,7 @@ dependencies = [ "bincode 1.3.3", "bytemuck", "const-crypto", + "data-layout", "ephemeral-rollups-pinocchio", "ephemeral-spl-api", "fslock", diff --git a/Cargo.toml b/Cargo.toml index 9bef85d..b51833a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,5 +54,5 @@ solana-transaction = "3.0.1" solana-system-interface = { version = "2.0.0", features = ["bincode"] } spl-token-interface = "2.0.0" magicblock-delegation-program-api = { version = "0.1.1", default-features = false } -bytemuck = { version = "1.23.1", features = ["derive"] } +bytemuck = { version = "1.25.0", features = ["derive"] } const-crypto = "0.3.0" diff --git a/e-token-api/Cargo.toml b/e-token-api/Cargo.toml index 4d9be97..104643d 100644 --- a/e-token-api/Cargo.toml +++ b/e-token-api/Cargo.toml @@ -13,7 +13,7 @@ pinocchio-log = { workspace = true } pinocchio-pubkey = { workspace = true } solana-address = { workspace = true, features = ["bytemuck"] } ephemeral-rollups-pinocchio = { workspace = true } -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } [lints] workspace = true diff --git a/e-token-api/src/error.rs b/e-token-api/src/error.rs index 69a2b93..3b9ff4e 100644 --- a/e-token-api/src/error.rs +++ b/e-token-api/src/error.rs @@ -3,8 +3,8 @@ use pinocchio::error::{ProgramError, ToStr}; #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum EphemeralSplError { - // invalid instruction data - InvalidInstruction = 1, + // invalid discriminator + InstructionNotFound = 1, // account already initialized / in use AlreadyInUse = 2, // Ephemeral ATA mismatch @@ -30,7 +30,7 @@ impl ToStr for EphemeralSplError { use EphemeralSplError::*; match self { - InvalidInstruction => "EphemeralSplError::InvalidInstruction", + InstructionNotFound => "EphemeralSplError::InstructionNotFound", AlreadyInUse => "EphemeralSplError::AlreadyInUse", EphemeralAtaMismatch => "EphemeralSplError::EphemeralAtaMismatch", MintMismatch => "EphemeralSplError::MintMismatch", @@ -45,7 +45,7 @@ impl core::convert::TryFrom for EphemeralSplError { type Error = ProgramError; fn try_from(value: u32) -> Result { match value { - 1 => Ok(EphemeralSplError::InvalidInstruction), + 1 => Ok(EphemeralSplError::InstructionNotFound), 2 => Ok(EphemeralSplError::AlreadyInUse), 3 => Ok(EphemeralSplError::EphemeralAtaMismatch), 4 => Ok(EphemeralSplError::MintMismatch), diff --git a/e-token-api/src/instruction.rs b/e-token-api/src/instruction.rs new file mode 100644 index 0000000..b37cf7c --- /dev/null +++ b/e-token-api/src/instruction.rs @@ -0,0 +1,221 @@ +use alloc::vec::Vec; + +/// +/// ESplInstruction defines the public instructions +/// +/// Reserved values: +/// - single: 196 : for DLP undelegatation callback used to restore delegated state +/// - range: (201.. =255) : for ESPL internal instructions +/// +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ESplInstruction { + /// 0 - InitializeEphemeralAta: initialize the ephemeral ATA account derived from [user, mint] + InitializeEphemeralAta = 0, + + /// 1 - InitializeGlobalVault: initialize the global vault [mint] PDA plus vault-owned Ephemeral ATA and vault ATA + InitializeGlobalVault = 1, + + /// 2 - DepositSplTokens: transfer tokens to global vault and increase EphemeralAta amount + /// Works for both standard EATA and shuttle EATA, as long as the data account is program-owned. + DepositSplTokens = 2, + + /// 3 - WithdrawSplTokens: transfer tokens from global vault back to user and decrease EphemeralAta amount + WithdrawSplTokens = 3, + + /// 4 - DelegateEphemeralAta: delegate the ephemeral ATA to a DLP program using PDA seeds + DelegateEphemeralAta = 4, + + /// 5 - UndelegateEphemeralAta: commit state and undelegate an ephemeral ATA via the delegation program + UndelegateEphemeralAta = 5, + + /// 6 - CreateEphemeralAtaPermission: create a permission account for the ephemeral ATA + /// Instruction data: + /// [0] bump + /// [1] MemberFlags bitfield encoded via MemberFlags::to_acl_flag_byte. + CreateEphemeralAtaPermission = 6, + + /// 7 - DelegateEphemeralAtaPermission: delegate the permission PDA for an ephemeral ATA + DelegateEphemeralAtaPermission = 7, + + /// 8 - UndelegateEphemeralAtaPermission: commit and undelegate the permission PDA + UndelegateEphemeralAtaPermission = 8, + + /// 9 - ResetEphemeralAtaPermission: reset permission members to creation-time defaults + /// Instruction data: + /// [0] bump + /// [1] MemberFlags bitfield encoded via MemberFlags::to_acl_flag_byte. + ResetEphemeralAtaPermission = 9, + + /// 10 - CloseEphemeralAta: close an empty ephemeral ATA and refund rent to recipient + CloseEphemeralAta = 10, + + /// 11 - InitializeShuttleEphemeralAta: initialize shuttle account derived from [owner, mint, shuttle_id] + /// Instruction data: + /// [0..4] shuttle_id (u32 LE) + /// [4] bump + InitializeShuttleEphemeralAta = 11, + + /// 12 - InitializeTransferQueue: initialize per-mint transfer queue PDA derived from [QUEUE_SEED, mint] + /// Instruction data: + /// [] default size (9728 bytes) + /// [0..4] optional queue size in bytes (u32 LE), 0 => default + InitializeTransferQueue = 12, + + /// 13 - DelegateShuttleEphemeralAta: delegate shuttle account to a DLP program using PDA seeds + DelegateShuttleEphemeralAta = 13, + + /// 14 - UndelegateAndCloseShuttleToOwner: revoke delegation on a shuttle ATA + /// and schedule settlement/close using an owner-owned destination token account. + UndelegateAndCloseShuttleToOwner = 14, + + /// 15 - MergeShuttleIntoEphemeralAta: transfer all shuttle ATA funds into destination ATA and keep shuttle account open + MergeShuttleIntoEphemeralAta = 15, + + /// 16 - DepositAndQueueTransfer: transfer tokens from signer into the vault ATA and enqueue one or more delayed transfers + /// Instruction data: + /// [0..8] amount (u64 LE) + /// [8..16] min_delay_ms (u64 LE), 0 => immediate + /// [16..24] max_delay_ms (u64 LE), must be >= min_delay_ms + /// [24..28] split count (u32 LE), must be >= 1 + /// [28] optional legacy flags (u8) + /// [28..36] optional client_ref_id (u64 LE) when flags are omitted + /// [29..37] optional client_ref_id (u64 LE) after legacy flags + DepositAndQueueTransfer = 16, + + /// 17 - EnsureTransferQueueCrank: ensure the per-mint recurring queue crank is scheduled + /// Instruction data: + /// [] + EnsureTransferQueueCrank = 17, + + /// 19 - DelegateTransferQueue: delegate the per-mint transfer queue PDA to the delegation program + /// Instruction data: + /// [] no instruction args + DelegateTransferQueue = 19, + + /// 20 - SponsoredLamportsTransfer: create a zero-data PDA derived from + /// [b"lamports", payer, destination, salt], fund it with the requested + /// lamports plus sponsored rent from the global rent PDA, delegate it, + /// then schedule post-delegation transfer + cleanup actions. + /// Instruction data: + /// [0..8] amount (u64 LE) + /// [8..40] salt ([u8; 32]) + SponsoredLamportsTransfer = 20, + + /// 23 - InitializeRentPda: initialize the global rent-sponsoring PDA derived from ["rent"] + /// Instruction data: + /// [] no instruction args + InitializeRentPda = 23, + + /// 24 - SetupAndDelegateShuttleEphemeralAtaWithMerge: initialize shuttle metadata/EATA/wallet ATA, + /// deposit tokens into the shuttle EATA through the global vault, sponsor delegation from + /// the global rent PDA, and schedule post-delegation merge + cleanup. + /// Instruction data: + /// [0..4] shuttle_id (u32 LE) + /// [4] shuttle metadata bump + /// [5..13] deposit amount (u64 LE) + /// [13..45] optional validator pubkey + SetupAndDelegateShuttleEphemeralAtaWithMerge = 24, + + /// 25 - DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer: + /// same setup/deposit/delegate flow as instruction 24, but instead of using instruction 24's + /// merge-to-destination behavior, the first post-delegation action restores the owner's + /// source token account by merging the shuttle balance back there, then a third post-delegation + /// action schedules a private transfer of the same amount to the destination owner's + /// canonical ATA. + /// SDK callers must account for that queued private transfer when calculating required source + /// balances and expected destination credits; the destination does not hold the final funds + /// immediately after the merge/cleanup steps. + /// Instruction data: + /// [0..4] shuttle_id (u32 LE) + /// [4..12] deposit amount (u64 LE) + /// [12..] len-prefixed optional validator pubkey bytes + /// [...] len-prefixed encrypted destination owner pubkey bytes + /// [...] len-prefixed encrypted packed suffix + /// (min_delay_ms:u64, max_delay_ms:u64, split:u32, client_ref_id?:u64) + /// Legacy payloads may still append flags before client_ref_id. + DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer = 25, + + /// 26 - WithdrawThroughDelegatedShuttleWithMerge: initialize shuttle metadata/EATA/wallet ATA, + /// sponsor delegation from the global rent PDA, then schedule a post-delegation transfer + /// from the owner ATA into the shuttle wallet ATA followed by shuttle undelegate + /// and close/refund. + /// Instruction data: + /// [0..4] shuttle_id (u32 LE) + /// [4] shuttle metadata bump + /// [5..13] transfer amount (u64 LE) + /// [13..45] optional validator pubkey + WithdrawThroughDelegatedShuttleWithMerge = 26, + + /// 27 - AllocateTransferQueue: allocates more space for the transfer queue + AllocateTransferQueue = 27, + + /// 28 - ProcessPendingTransferQueueRefill: permissionless idempotent helper that + /// checks the queue-refill-state PDA and, when pending, tops up the queue + /// lamports from the global rent PDA. + /// Instruction data: + /// [] no instruction args + ProcessPendingTransferQueueRefill = 28, +} + +impl ESplInstruction { + #[inline(always)] + pub const fn discriminator(self) -> u8 { + self as u8 + } + + #[inline(always)] + pub const fn to_bytes(self) -> [u8; 8] { + [self.discriminator(), 0, 0, 0, 0, 0, 0, 0] + } + + #[inline(always)] + pub fn to_vec(self) -> Vec { + self.to_bytes().to_vec() + } + + #[inline(always)] + pub fn with_data(self, instruction_data: &[u8]) -> Vec { + let mut data = Vec::with_capacity(8 + instruction_data.len()); + data.extend_from_slice(&self.to_bytes()); + data.extend_from_slice(instruction_data); + data + } +} + +impl TryFrom for ESplInstruction { + type Error = (); + + #[inline(always)] + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::InitializeEphemeralAta), + 1 => Ok(Self::InitializeGlobalVault), + 2 => Ok(Self::DepositSplTokens), + 3 => Ok(Self::WithdrawSplTokens), + 4 => Ok(Self::DelegateEphemeralAta), + 5 => Ok(Self::UndelegateEphemeralAta), + 6 => Ok(Self::CreateEphemeralAtaPermission), + 7 => Ok(Self::DelegateEphemeralAtaPermission), + 8 => Ok(Self::UndelegateEphemeralAtaPermission), + 9 => Ok(Self::ResetEphemeralAtaPermission), + 10 => Ok(Self::CloseEphemeralAta), + 11 => Ok(Self::InitializeShuttleEphemeralAta), + 12 => Ok(Self::InitializeTransferQueue), + 13 => Ok(Self::DelegateShuttleEphemeralAta), + 14 => Ok(Self::UndelegateAndCloseShuttleToOwner), + 15 => Ok(Self::MergeShuttleIntoEphemeralAta), + 16 => Ok(Self::DepositAndQueueTransfer), + 17 => Ok(Self::EnsureTransferQueueCrank), + 19 => Ok(Self::DelegateTransferQueue), + 20 => Ok(Self::SponsoredLamportsTransfer), + 23 => Ok(Self::InitializeRentPda), + 24 => Ok(Self::SetupAndDelegateShuttleEphemeralAtaWithMerge), + 25 => Ok(Self::DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer), + 26 => Ok(Self::WithdrawThroughDelegatedShuttleWithMerge), + 27 => Ok(Self::AllocateTransferQueue), + 28 => Ok(Self::ProcessPendingTransferQueueRefill), + _ => Err(()), + } + } +} diff --git a/e-token-api/src/lib.rs b/e-token-api/src/lib.rs index e231097..a78cabb 100644 --- a/e-token-api/src/lib.rs +++ b/e-token-api/src/lib.rs @@ -1,171 +1,24 @@ #![no_std] +extern crate alloc; // Single source of truth for the e-ephemeral-token program ID. // Keep this in a separate rlib crate so tests and clients can link it while // the on-chain program crate stays cdylib-only. pub mod consts; pub mod error; +pub mod instruction; pub mod requires; pub mod state; pub mod program { pub use ephemeral_rollups_pinocchio::consts::DELEGATION_PROGRAM_ID; } +mod pod_view; + +pub use pod_view::PodView; + solana_address::declare_id!("SPLxh1LVZzEkX99H6rqYizhytLWPZVV296zyYDPagv2"); /// Re-exported `Address` type from solana-address for convenience. /// Used throughout the codebase for account address representations. pub use solana_address::Address; - -/// Instruction discriminators for the Ephemeral SPL program. -/// Keep in sync with the on-chain program dispatcher. -pub mod instruction { - /// 0 - InitializeEphemeralAta: initialize the ephemeral ATA account derived from [user, mint] - pub const INITIALIZE_EPHEMERAL_ATA: u8 = 0; - /// 1 - InitializeGlobalVault: initialize the global vault [mint] PDA plus vault-owned Ephemeral ATA and vault ATA - pub const INITIALIZE_GLOBAL_VAULT: u8 = 1; - /// 2 - DepositSplTokens: transfer tokens to global vault and increase EphemeralAta amount - /// Works for both standard EATA and shuttle EATA, as long as the data account is program-owned. - pub const DEPOSIT_SPL_TOKENS: u8 = 2; - /// 3 - WithdrawSplTokens: transfer tokens from global vault back to user and decrease EphemeralAta amount - pub const WITHDRAW_SPL_TOKENS: u8 = 3; - /// 4 - DelegateEphemeralAta: delegate the ephemeral ATA to a DLP program using PDA seeds - pub const DELEGATE_EPHEMERAL_ATA: u8 = 4; - /// 5 - UndelegateEphemeralAta: commit state and undelegate an ephemeral ATA via the delegation program - pub const UNDELEGATE_EPHEMERAL_ATA: u8 = 5; - /// 6 - CreateEphemeralAtaPermission: create a permission account for the ephemeral ATA - /// Instruction data: - /// [0] bump - /// [1] MemberFlags bitfield encoded via MemberFlags::to_acl_flag_byte. - pub const CREATE_EPHEMERAL_ATA_PERMISSION: u8 = 6; - /// 7 - DelegateEphemeralAtaPermission: delegate the permission PDA for an ephemeral ATA - pub const DELEGATE_EPHEMERAL_ATA_PERMISSION: u8 = 7; - /// 8 - UndelegateEphemeralAtaPermission: commit and undelegate the permission PDA - pub const UNDELEGATE_EPHEMERAL_ATA_PERMISSION: u8 = 8; - /// 9 - ResetEphemeralAtaPermission: reset permission members to creation-time defaults - /// Instruction data: - /// [0] bump - /// [1] MemberFlags bitfield encoded via MemberFlags::to_acl_flag_byte. - pub const RESET_EPHEMERAL_ATA_PERMISSION: u8 = 9; - /// 10 - CloseEphemeralAta: close an empty ephemeral ATA and refund rent to recipient - pub const CLOSE_EPHEMERAL_ATA: u8 = 10; - /// 11 - InitializeShuttleEphemeralAta: initialize shuttle account derived from [owner, mint, shuttle_id] - /// Instruction data: - /// [0..4] shuttle_id (u32 LE) - /// [4] bump - pub const INITIALIZE_SHUTTLE_EPHEMERAL_ATA: u8 = 11; - /// 12 - InitializeTransferQueue: initialize per-mint transfer queue PDA derived from [QUEUE_SEED, mint] - /// Instruction data: - /// [] default size (9728 bytes) - /// [0..4] optional queue size in bytes (u32 LE), 0 => default - pub const INITIALIZE_TRANSFER_QUEUE: u8 = 12; - /// 13 - DelegateShuttleEphemeralAta: delegate shuttle account to a DLP program using PDA seeds - pub const DELEGATE_SHUTTLE_EPHEMERAL_ATA: u8 = 13; - /// 14 - UndelegateAndCloseShuttleToOwner: revoke delegation on a shuttle ATA - /// and schedule settlement/close using an owner-owned destination token account. - pub const UNDELEGATE_AND_CLOSE_SHUTTLE_TO_OWNER: u8 = 14; - /// 15 - MergeShuttleIntoEphemeralAta: transfer all shuttle ATA funds into destination ATA and keep shuttle account open - pub const MERGE_SHUTTLE_INTO_EPHEMERAL_ATA: u8 = 15; - /// 16 - DepositAndQueueTransfer: transfer tokens from signer into the vault ATA and enqueue one or more delayed transfers - /// Instruction data: - /// [0..8] amount (u64 LE) - /// [8..16] min_delay_ms (u64 LE), 0 => immediate - /// [16..24] max_delay_ms (u64 LE), must be >= min_delay_ms - /// [24..28] split count (u32 LE), must be >= 1 - /// [28] optional legacy flags (u8) - /// [28..36] optional client_ref_id (u64 LE) when flags are omitted - /// [29..37] optional client_ref_id (u64 LE) after legacy flags - pub const DEPOSIT_AND_QUEUE_TRANSFER: u8 = 16; - /// 17 - EnsureTransferQueueCrank: ensure the per-mint recurring queue crank is scheduled - /// Instruction data: - /// [] - pub const ENSURE_TRANSFER_QUEUE_CRANK: u8 = 17; - /// 19 - DelegateTransferQueue: delegate the per-mint transfer queue PDA to the delegation program - /// Instruction data: - /// [] no instruction args - pub const DELEGATE_TRANSFER_QUEUE: u8 = 19; - /// 20 - SponsoredLamportsTransfer: create a zero-data PDA derived from - /// [b"lamports", payer, destination, salt], fund it with the requested - /// lamports plus sponsored rent from the global rent PDA, delegate it, - /// then schedule post-delegation transfer + cleanup actions. - /// Instruction data: - /// [0..8] amount (u64 LE) - /// [8..40] salt ([u8; 32]) - pub const SPONSORED_LAMPORTS_TRANSFER: u8 = 20; - /// 23 - InitializeRentPda: initialize the global rent-sponsoring PDA derived from ["rent"] - /// Instruction data: - /// [] no instruction args - pub const INITIALIZE_RENT_PDA: u8 = 23; - /// 24 - SetupAndDelegateShuttleEphemeralAtaWithMerge: initialize shuttle metadata/EATA/wallet ATA, - /// deposit tokens into the shuttle EATA through the global vault, sponsor delegation from - /// the global rent PDA, and schedule post-delegation merge + cleanup. - /// Instruction data: - /// [0..4] shuttle_id (u32 LE) - /// [4] shuttle metadata bump - /// [5..13] deposit amount (u64 LE) - /// [13..45] optional validator pubkey - pub const SETUP_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE: u8 = 24; - /// 25 - DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer: - /// same setup/deposit/delegate flow as instruction 24, but instead of using instruction 24's - /// merge-to-destination behavior, the first post-delegation action restores the owner's - /// source token account by merging the shuttle balance back there, then a third post-delegation - /// action schedules a private transfer of the same amount to the destination owner's - /// canonical ATA. - /// SDK callers must account for that queued private transfer when calculating required source - /// balances and expected destination credits; the destination does not hold the final funds - /// immediately after the merge/cleanup steps. - /// Instruction data: - /// [0..4] shuttle_id (u32 LE) - /// [4..12] deposit amount (u64 LE) - /// [12..] len-prefixed optional validator pubkey bytes - /// [...] len-prefixed encrypted destination owner pubkey bytes - /// [...] len-prefixed encrypted packed suffix - /// (min_delay_ms:u64, max_delay_ms:u64, split:u32, client_ref_id?:u64) - /// Legacy payloads may still append flags before client_ref_id. - pub const DEPOSIT_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE_AND_PRIVATE_TRANSFER: u8 = 25; - /// 26 - WithdrawThroughDelegatedShuttleWithMerge: initialize shuttle metadata/EATA/wallet ATA, - /// sponsor delegation from the global rent PDA, then schedule a post-delegation transfer - /// from the owner ATA into the shuttle wallet ATA followed by shuttle undelegate - /// and close/refund. - /// Instruction data: - /// [0..4] shuttle_id (u32 LE) - /// [4] shuttle metadata bump - /// [5..13] transfer amount (u64 LE) - /// [13..45] optional validator pubkey - pub const WITHDRAW_THROUGH_DELEGATED_SHUTTLE_WITH_MERGE: u8 = 26; - /// 27 - AllocateTransferQueue: allocates more space for the transfer queue - pub const ALLOCATE_TRANSFER_QUEUE: u8 = 27; - /// 28 - ProcessPendingTransferQueueRefill: permissionless idempotent helper that - /// checks the queue-refill-state PDA and, when pending, tops up the queue - /// lamports from the global rent PDA. - /// Instruction data: - /// [] no instruction args - pub const PROCESS_PENDING_TRANSFER_QUEUE_REFILL: u8 = 28; - - /// Internal-only instruction discriminators used by the on-chain program. - pub mod internal { - /// 196 - UndelegationCallback: delegation-program callback used to restore delegated state. - pub const UNDELEGATION_CALLBACK: u8 = 196; - /// 197 - SettleAndCloseShuttleIntent: Magic standalone action that withdraws any - /// remaining shuttle balance to the supplied destination token account, then - /// closes the shuttle accounts. - pub const SETTLE_AND_CLOSE_SHUTTLE_INTENT: u8 = 197; - /// 198 - ExecuteReadyQueuedTransfer: Magic standalone action that settles one queued transfer. - pub const EXECUTE_READY_QUEUED_TRANSFER: u8 = 198; - /// 199 - ProcessTransferQueueTick: recurring crank callback that checks a queue and schedules settlement. - pub const PROCESS_TRANSFER_QUEUE_TICK: u8 = 199; - /// 200 - TransferLamportsPda: post-delegation action that transfers the - /// requested lamports from the delegated zero-data PDA to the - /// destination base-layer account. - pub const TRANSFER_LAMPORTS_PDA: u8 = 200; - /// 202 - UndelegateLamportsPda: post-delegation action that commits and - /// undelegates the lamports PDA, then schedules the close/refund intent. - pub const UNDELEGATE_LAMPORTS_PDA: u8 = 202; - /// 203 - CloseLamportsPdaIntent: post-undelegate Magic intent that - /// refunds the sponsored rent to the global rent PDA and closes the PDA. - pub const CLOSE_LAMPORTS_PDA_INTENT: u8 = 203; - /// 204 - MarkTransferQueueRefillPending: Magic standalone action that - /// sets the per-queue refill-state pending flag. - pub const MARK_TRANSFER_QUEUE_REFILL_PENDING: u8 = 204; - } -} diff --git a/e-token-api/src/pod_view.rs b/e-token-api/src/pod_view.rs new file mode 100644 index 0000000..13f07dc --- /dev/null +++ b/e-token-api/src/pod_view.rs @@ -0,0 +1,145 @@ +use alloc::vec::Vec; +use pinocchio::error::ProgramError; + +/// SAFETY +/// +/// This program uses POD structs for account and instruction data that are +/// read/written via raw byte copies (bytemuck, raw pointers, or direct memory +/// views). This document establishes the safety and correctness rules for such +/// usage, with special attention to endianness, alignment, and representation. +/// +/// Solana BPF Memory Model & Endianness Guarantees +/// =============================================== +/// +/// The Solana BPF VM uses a little-endian memory model. As a result, all +/// multi-byte scalar types (integers and floats) are stored in memory using +/// little-endian byte order. +/// +/// When this program serializes POD types by copying their raw memory layout +/// (e.g. via bytemuck::bytes_of or ptr::copy_nonoverlapping), the resulting +/// byte sequence is also little-endian, because it directly reflects the +/// machine-level representation. +/// +/// But also note that although the Solana VM is little-endian, Solana does not mandate any +/// serialization format or endianness for account or instruction data. A program +/// may choose to serialize its data in any order it wants. The only rule is: the +/// reader must interpret the data consistently with the writer. +/// +/// However, in this codebase we serialize POD types by copying their raw memory +/// bytes directly. Since the machine representation is little-endian, this implicitly +/// produces a little-endian serialization format. +/// +/// Alignment +/// ========= +/// +/// POD structs rely on correct alignment whenever they are accessed through "typed" +/// pointers i.e both during serialization and deserialization (even though in POD's case, +/// serialization/deserialization are merely reinterpretion of the raw memory). +/// +/// That basically means, if a struct has alignment n, then: +/// +/// - The starting address of account buffer must be aligned to n-byte boundary. +/// - The struct's fields must not cause unintended padding beyond what #[repr(C)] +/// automatically defines and each field must be aligned to their own alignment. +/// - In our codebase, we however explicitly define the padding for two reasons: +/// - The padding is visible. +/// - And since the padding is visible and has a name, we can easily initialize it +/// with all zeroes without using any dirty trick. Note that uninitialized paddings +/// are dangerous and would invoke UB (Rust must be inheriting this UB behaviour +/// from C++ indirectly through LLVM). +/// +/// Our codebase assumes that both `account data` and `instruction data` are aligned to +/// 8-byte boundary and therefore all structs/types have either 8-byte alignment requirement +/// or weaker one. The `bytemuck` crates enforces the alignment requirements, so we do not +/// have to do it ourselves. +/// +/// Avoid char and bool +/// =================== +/// +/// Two Rust primitives must be AVOIDED in low-level serialization and POD +/// layouts: +/// - char +/// - bool +/// +/// These two are "disgusting types" in the context of raw byte-level +/// programming because NOT all bit-patterns are valid values for them: +/// - bool only permits 0 and 1; all other bytes are invalid. +/// - char permits only valid Unicode scalar values; most u32 patterns +/// represent invalid char. +/// +/// As a result, neither char nor bool can be safely reinterpreted from +/// raw bytes, cannot be Pod, and must not appear in account or instruction +/// layouts. Use u8 instead. Note that `#[repr(u8)] enum` is disgusting +/// type as well, so we cannot use that either. +/// +/// Ref: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html +/// +pub trait PodView { + /// The exact size of the POD type in bytes. + /// + /// This is used to create new accounts and to ensure that the buffer + /// we write to or read from matches the expected layout exactly. + const SPACE: usize; + const ALIGN: usize; + + fn to_bytes(&self) -> Vec + where + Self: bytemuck::Pod, + { + bytemuck::bytes_of(self).to_vec() + } + + /// Copy the raw bytes of Self into the given mutable buffer. + /// + /// This is a *copy*, not a cast. The buffer must be exactly Self::SPACE + /// bytes long. On success, the buffer will contain a byte-for-byte + /// representation of this struct, else it will return ErrorCode::SizeMismatch. + fn try_copy_to(&self, buffer: &mut [u8]) -> Result<(), ProgramError>; + + /// This function performs a zero-copy cast from [u8] to &Self. + /// + /// The buffer must: + /// - be exactly Self::SPACE bytes long + /// - be properly aligned (guaranteed for Solana account data) + /// - contain valid POD bytes for Self + fn try_view_from(buffer: &[u8]) -> Result<&Self, ProgramError>; + + /// Mutable version of try_view_from. + fn try_view_from_mut(buffer: &mut [u8]) -> Result<&mut Self, ProgramError>; + + // During testing, the account/ix data may not be properly aligned. + // In that case, we could create a "copy" instead of a "view" using this + // function that takes care of provided possibly-unaligned_buffer. + // #[cfg(feature = "unit_test_config")] + // fn try_from_unaligned(unaligned_buffer: &[u8]) -> Result + // where + // Self: Sized; +} + +impl PodView for T { + const SPACE: usize = core::mem::size_of::(); + const ALIGN: usize = core::mem::align_of::(); + + fn try_copy_to(&self, buffer: &mut [u8]) -> Result<(), ProgramError> { + if buffer.len() != Self::SPACE { + return Err(ProgramError::InvalidArgument); + } + let src = bytemuck::bytes_of(self); + buffer.copy_from_slice(src); + Ok(()) + } + + fn try_view_from(buffer: &[u8]) -> Result<&Self, ProgramError> { + bytemuck::try_from_bytes(buffer).map_err(|_| ProgramError::InvalidArgument) + } + + fn try_view_from_mut(buffer: &mut [u8]) -> Result<&mut Self, ProgramError> { + bytemuck::try_from_bytes_mut(buffer).map_err(|_| ProgramError::InvalidArgument) + } + + //#[cfg(feature = "unit_test_config")] + //fn try_from_unaligned(possibly_unaligned_buffer: &[u8]) -> Result { + // bytemuck::try_pod_read_unaligned(possibly_unaligned_buffer) + // .map_err(|_| ProgramError::InvalidArgument) + //} +} diff --git a/e-token/Cargo.toml b/e-token/Cargo.toml index acf8a80..ead4bd8 100644 --- a/e-token/Cargo.toml +++ b/e-token/Cargo.toml @@ -9,12 +9,13 @@ edition = { workspace = true } readme = "./README.md" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [features] logging = [] [dependencies] +bytemuck = { workspace = true } const-crypto = { workspace = true } ephemeral-rollups-pinocchio = { workspace = true } pinocchio = { workspace = true } @@ -28,6 +29,7 @@ solana-pubkey = { workspace = true } magicblock-delegation-program-api = { workspace = true, default-features = false, features = [ "cpi", ] } +data-layout = { path = "../data-layout" } [dev-dependencies] bytemuck = { workspace = true } diff --git a/e-token/src/entrypoint.rs b/e-token/src/entrypoint.rs index a9b7c23..0b7751d 100644 --- a/e-token/src/entrypoint.rs +++ b/e-token/src/entrypoint.rs @@ -1,6 +1,9 @@ -use ephemeral_spl_api::error::EphemeralSplError; -use ephemeral_spl_api::instruction::{self, internal}; +use ephemeral_spl_api::instruction::ESplInstruction; +use ephemeral_spl_api::require_ge; +use ephemeral_spl_api::{error::EphemeralSplError, require}; + use { + crate::instruction::ESplInternalInstruction, crate::processor::*, core::{mem::MaybeUninit, slice::from_raw_parts}, pinocchio::{ @@ -42,233 +45,266 @@ fn log_error(error: &ProgramError) { /// Process an instruction. #[inline(never)] pub fn process_instruction(accounts: &[AccountView], instruction_data: &[u8]) -> ProgramResult { - let result = inner_process_instruction(accounts, instruction_data); + require!( + !instruction_data.is_empty(), + ProgramError::InvalidInstructionData + ); + + let result = { + // UndelegationCallback is the first internal type, so anything less than that is public + // instruction + if instruction_data[0] < ESplInternalInstruction::UndelegationCallback.discriminator() { + process_public_instruction(accounts, instruction_data) + } else { + process_internal_instruction(accounts, instruction_data) + } + }; result.inspect_err(log_error) } -/// Process an instruction. +/// Process public instruction #[inline(never)] -pub(crate) fn inner_process_instruction( - accounts: &[AccountView], - instruction_data: &[u8], -) -> ProgramResult { - let [discriminator, instruction_data @ ..] = instruction_data else { - return Err(EphemeralSplError::InvalidInstruction.into()); - }; +fn process_public_instruction(accounts: &[AccountView], instruction_data: &[u8]) -> ProgramResult { + require_ge!( + instruction_data.len(), + 8, + ProgramError::InvalidInstructionData + ); + + let (discriminator, data) = instruction_data.split_at(8); - match *discriminator { - instruction::INITIALIZE_EPHEMERAL_ATA => { + match ESplInstruction::try_from(discriminator[0]) + .map_err(|_| EphemeralSplError::InstructionNotFound)? + { + ESplInstruction::InitializeEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: InitializeEphemeralAta"); - process_initialize_ephemeral_ata(accounts, instruction_data) + process_initialize_ephemeral_ata(accounts, data) } - instruction::INITIALIZE_GLOBAL_VAULT => { + ESplInstruction::InitializeGlobalVault => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: InitializeGlobalVault"); - process_initialize_global_vault(accounts, instruction_data) + process_initialize_global_vault(accounts, data) } - instruction::DEPOSIT_SPL_TOKENS => { + ESplInstruction::DepositSplTokens => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DepositSplTokens"); - process_deposit_spl_tokens(accounts, instruction_data) + process_deposit_spl_tokens(accounts, data) } - instruction::WITHDRAW_SPL_TOKENS => { + ESplInstruction::WithdrawSplTokens => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: WithdrawSplTokens"); - process_withdraw_spl_tokens(accounts, instruction_data) + process_withdraw_spl_tokens(accounts, data) } - instruction::DELEGATE_EPHEMERAL_ATA => { + ESplInstruction::DelegateEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DelegateEphemeralAta"); - process_delegate_ephemeral_ata(accounts, instruction_data) + process_delegate_ephemeral_ata(accounts, data) } - instruction::UNDELEGATE_EPHEMERAL_ATA => { + ESplInstruction::UndelegateEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: UndelegateEphemeralAta"); - process_undelegate_ephemeral_ata(accounts, instruction_data) + process_undelegate_ephemeral_ata(accounts, data) } - instruction::CREATE_EPHEMERAL_ATA_PERMISSION => { + ESplInstruction::CreateEphemeralAtaPermission => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: CreateEphemeralAtaPermission"); - process_create_ephemeral_ata_permission(accounts, instruction_data) + process_create_ephemeral_ata_permission(accounts, data) } - instruction::DELEGATE_EPHEMERAL_ATA_PERMISSION => { + ESplInstruction::DelegateEphemeralAtaPermission => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DelegateEphemeralAtaPermission"); - process_delegate_ephemeral_ata_permission(accounts, instruction_data) + process_delegate_ephemeral_ata_permission(accounts, data) } - instruction::UNDELEGATE_EPHEMERAL_ATA_PERMISSION => { + ESplInstruction::UndelegateEphemeralAtaPermission => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: UndelegateEphemeralAtaPermission"); - process_undelegate_ephemeral_ata_permission(accounts, instruction_data) + process_undelegate_ephemeral_ata_permission(accounts, data) } - instruction::RESET_EPHEMERAL_ATA_PERMISSION => { + ESplInstruction::ResetEphemeralAtaPermission => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: ResetEphemeralAtaPermission"); - process_reset_ephemeral_ata_permission(accounts, instruction_data) + process_reset_ephemeral_ata_permission(accounts, data) } - instruction::CLOSE_EPHEMERAL_ATA => { + ESplInstruction::CloseEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: CloseEphemeralAta"); - process_close_ephemeral_ata(accounts, instruction_data) + process_close_ephemeral_ata(accounts, data) } - instruction::INITIALIZE_SHUTTLE_EPHEMERAL_ATA => { + ESplInstruction::InitializeShuttleEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: InitializeShuttleEphemeralAta"); - process_initialize_shuttle_ephemeral_ata(accounts, instruction_data) + process_initialize_shuttle_ephemeral_ata(accounts, data) } - instruction::INITIALIZE_TRANSFER_QUEUE => { + ESplInstruction::InitializeTransferQueue => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: InitializeTransferQueue"); - process_initialize_transfer_queue(accounts, instruction_data) + process_initialize_transfer_queue(accounts, data) } - instruction::DELEGATE_SHUTTLE_EPHEMERAL_ATA => { + ESplInstruction::DelegateShuttleEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DelegateShuttleEphemeralAta"); - process_delegate_shuttle_ephemeral_ata(accounts, instruction_data) + process_delegate_shuttle_ephemeral_ata(accounts, data) } - instruction::SETUP_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE => { + ESplInstruction::SetupAndDelegateShuttleEphemeralAtaWithMerge => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: SetupAndDelegateShuttleEphemeralAtaWithMerge"); - process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge( - accounts, - instruction_data, - ) + process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge(accounts, data) } - instruction::DEPOSIT_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE_AND_PRIVATE_TRANSFER => { + ESplInstruction::DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer => { #[cfg(feature = "logging")] pinocchio_log::log!( "Instruction: DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer" ); process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer( - accounts, - instruction_data, + accounts, data, ) } - instruction::WITHDRAW_THROUGH_DELEGATED_SHUTTLE_WITH_MERGE => { + ESplInstruction::WithdrawThroughDelegatedShuttleWithMerge => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: WithdrawThroughDelegatedShuttleWithMerge"); - process_withdraw_through_delegated_shuttle_with_merge(accounts, instruction_data) + process_withdraw_through_delegated_shuttle_with_merge(accounts, data) } - instruction::UNDELEGATE_AND_CLOSE_SHUTTLE_TO_OWNER => { + ESplInstruction::UndelegateAndCloseShuttleToOwner => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: UndelegateAndCloseShuttleToOwner"); - process_undelegate_and_close_shuttle_to_owner(accounts, instruction_data) + process_undelegate_and_close_shuttle_to_owner(accounts, data) } - instruction::MERGE_SHUTTLE_INTO_EPHEMERAL_ATA => { + ESplInstruction::MergeShuttleIntoEphemeralAta => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: MergeShuttleIntoEphemeralAta"); - process_merge_shuttle_into_ephemeral_ata(accounts, instruction_data) + process_merge_shuttle_into_ephemeral_ata(accounts, data) } - instruction::DEPOSIT_AND_QUEUE_TRANSFER => { + ESplInstruction::DepositAndQueueTransfer => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DepositAndQueueTransfer"); - process_deposit_and_queue_transfer(accounts, instruction_data) + process_deposit_and_queue_transfer(accounts, data) } - instruction::ENSURE_TRANSFER_QUEUE_CRANK => { + ESplInstruction::EnsureTransferQueueCrank => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: EnsureTransferQueueCrank"); - process_ensure_transfer_queue_crank(accounts, instruction_data) + process_ensure_transfer_queue_crank(accounts, data) } - instruction::DELEGATE_TRANSFER_QUEUE => { + ESplInstruction::DelegateTransferQueue => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: DelegateTransferQueue"); - process_delegate_transfer_queue(accounts, instruction_data) + process_delegate_transfer_queue(accounts, data) } - instruction::SPONSORED_LAMPORTS_TRANSFER => { + ESplInstruction::SponsoredLamportsTransfer => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: SponsoredLamportsTransfer"); - process_sponsored_lamports_transfer(accounts, instruction_data) + process_sponsored_lamports_transfer(accounts, data) } - instruction::INITIALIZE_RENT_PDA => { + ESplInstruction::InitializeRentPda => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: InitializeRentPda"); - process_initialize_rent_pda(accounts, instruction_data) + process_initialize_rent_pda(accounts, data) } - instruction::ALLOCATE_TRANSFER_QUEUE => { + ESplInstruction::AllocateTransferQueue => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: AllocateTransferQueue"); - process_allocate_transfer_queue(accounts, instruction_data) + process_allocate_transfer_queue(accounts, data) } - instruction::PROCESS_PENDING_TRANSFER_QUEUE_REFILL => { + ESplInstruction::ProcessPendingTransferQueueRefill => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: ProcessPendingTransferQueueRefill"); - process_pending_transfer_queue_refill(accounts, instruction_data) + process_pending_transfer_queue_refill(accounts, data) } - internal::UNDELEGATION_CALLBACK => { + } +} + +/// Process internal instruction +#[inline(never)] +fn process_internal_instruction( + accounts: &[AccountView], + instruction_data: &[u8], +) -> ProgramResult { + require_ge!( + instruction_data.len(), + 8, + ProgramError::InvalidInstructionData + ); + + let (discriminator, data) = instruction_data.split_at(8); + + match ESplInternalInstruction::try_from(discriminator[0]) + .map_err(|_| EphemeralSplError::InstructionNotFound)? + { + ESplInternalInstruction::UndelegationCallback => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: UndelegationCallback"); - - process_undelegation_callback(accounts, instruction_data) + // this is a special instruction: in this case the discriminator is just one byte + // and the rest is data. + let (_discriminator, data) = instruction_data.split_at(1); + process_undelegation_callback(accounts, data) } - internal::SETTLE_AND_CLOSE_SHUTTLE_INTENT => { + ESplInternalInstruction::SettleAndCloseShuttleIntent => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: SettleAndCloseShuttleIntent"); - process_close_shuttle_ata_intent(accounts, instruction_data) + process_close_shuttle_ata_intent(accounts, data) } - internal::EXECUTE_READY_QUEUED_TRANSFER => { + ESplInternalInstruction::ExecuteReadyQueuedTransfer => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: ExecuteReadyQueuedTransfer"); - process_execute_ready_queued_transfer(accounts, instruction_data) + process_execute_ready_queued_transfer(accounts, data) } - internal::PROCESS_TRANSFER_QUEUE_TICK => { + ESplInternalInstruction::ProcessTransferQueueTick => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: ProcessTransferQueueTick"); - process_transfer_queue_tick(accounts, instruction_data) + process_transfer_queue_tick(accounts, data) } - internal::TRANSFER_LAMPORTS_PDA => { + ESplInternalInstruction::TransferLamportsPda => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: TransferLamportsPda"); - process_transfer_lamports_pda(accounts, instruction_data) + process_transfer_lamports_pda(accounts, data) } - internal::UNDELEGATE_LAMPORTS_PDA => { + ESplInternalInstruction::UndelegateLamportsPda => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: UndelegateLamportsPda"); - process_undelegate_lamports_pda(accounts, instruction_data) + process_undelegate_lamports_pda(accounts, data) } - internal::CLOSE_LAMPORTS_PDA_INTENT => { + ESplInternalInstruction::CloseLamportsPdaIntent => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: CloseLamportsPdaIntent"); - process_close_lamports_pda_intent(accounts, instruction_data) + process_close_lamports_pda_intent(accounts, data) } - internal::MARK_TRANSFER_QUEUE_REFILL_PENDING => { + ESplInternalInstruction::MarkTransferQueueRefillPending => { #[cfg(feature = "logging")] pinocchio_log::log!("Instruction: MarkTransferQueueRefillPending"); - process_mark_transfer_queue_refill_pending(accounts, instruction_data) + process_mark_transfer_queue_refill_pending(accounts, data) } - _ => Err(EphemeralSplError::InvalidInstruction.into()), } } diff --git a/e-token/src/instruction.rs b/e-token/src/instruction.rs new file mode 100644 index 0000000..edf212c --- /dev/null +++ b/e-token/src/instruction.rs @@ -0,0 +1,84 @@ +use alloc::vec::Vec; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum ESplInternalInstruction { + /// 196 - DLP undelegatation callback used to restore delegated state. + /// The reason this discriminator is 196, not 200 or so, is because + /// its value is decided by the DLP program. So the discontinuity is intentional + /// and it is to indicate its origin. + UndelegationCallback = 196, + + /// 201 - SettleAndCloseShuttleIntent: Magic standalone action that withdraws any + /// remaining shuttle balance to the supplied destination token account, then + /// closes the shuttle accounts. + SettleAndCloseShuttleIntent = 201, + + /// 202 - ExecuteReadyQueuedTransfer: Magic standalone action that settles one queued transfer. + ExecuteReadyQueuedTransfer = 202, + + /// 203 - ProcessTransferQueueTick: recurring crank callback that checks a queue and schedules settlement. + ProcessTransferQueueTick = 203, + + /// 204 - TransferLamportsPda: post-delegation action that transfers the + /// requested lamports from the delegated zero-data PDA to the + /// destination base-layer account. + TransferLamportsPda = 204, + + /// 205 - UndelegateLamportsPda: post-delegation action that commits and + /// undelegates the lamports PDA, then schedules the close/refund intent. + UndelegateLamportsPda = 205, + + /// 206 - CloseLamportsPdaIntent: post-undelegate Magic intent that + /// refunds the sponsored rent to the global rent PDA and closes the PDA. + CloseLamportsPdaIntent = 206, + + /// 207 - MarkTransferQueueRefillPending: Magic standalone action that + /// sets the per-queue refill-state pending flag. + MarkTransferQueueRefillPending = 207, +} + +impl ESplInternalInstruction { + #[inline(always)] + pub(crate) const fn discriminator(self) -> u8 { + self as u8 + } + + #[inline(always)] + pub(crate) const fn to_bytes(self) -> [u8; 8] { + [self.discriminator(), 0, 0, 0, 0, 0, 0, 0] + } + + #[inline(always)] + #[allow(dead_code)] + pub(crate) fn to_vec(self) -> Vec { + self.to_bytes().to_vec() + } + + #[inline(always)] + pub(crate) fn with_data(self, instruction_data: &[u8]) -> Vec { + let mut data = Vec::with_capacity(8 + instruction_data.len()); + data.extend_from_slice(&self.to_bytes()); + data.extend_from_slice(instruction_data); + data + } +} + +impl TryFrom for ESplInternalInstruction { + type Error = (); + + #[inline(always)] + fn try_from(value: u8) -> Result { + match value { + 196 => Ok(Self::UndelegationCallback), + 201 => Ok(Self::SettleAndCloseShuttleIntent), + 202 => Ok(Self::ExecuteReadyQueuedTransfer), + 203 => Ok(Self::ProcessTransferQueueTick), + 204 => Ok(Self::TransferLamportsPda), + 205 => Ok(Self::UndelegateLamportsPda), + 206 => Ok(Self::CloseLamportsPdaIntent), + 207 => Ok(Self::MarkTransferQueueRefillPending), + _ => Err(()), + } + } +} diff --git a/e-token/src/lib.rs b/e-token/src/lib.rs index 2ba8440..95655e4 100644 --- a/e-token/src/lib.rs +++ b/e-token/src/lib.rs @@ -2,7 +2,14 @@ extern crate alloc; mod entrypoint; +mod instruction; mod processor; pub use crate::entrypoint::process_instruction; pub use ephemeral_spl_api::ID; + +pub use processor::{ + DelegateArgs, DelegateShuttleArgs, DepositAndDelegateShuttleArgs, + DepositAndDelegateShuttleWithPrivateTransferArgs, DepositAndQueueTransferArgs, + ExecuteQueuedTransferArgs, InitializeTransferQueueArgs, +}; diff --git a/e-token/src/processor/create_ephemeral_ata_permission.rs b/e-token/src/processor/create_ephemeral_ata_permission.rs index e6380c0..a1d3450 100644 --- a/e-token/src/processor/create_ephemeral_ata_permission.rs +++ b/e-token/src/processor/create_ephemeral_ata_permission.rs @@ -1,11 +1,11 @@ -use core::marker::PhantomData; +use bytemuck::{Pod, Zeroable}; use ephemeral_rollups_pinocchio::acl::{ consts::PERMISSION_PROGRAM_ID, instruction::CreatePermissionCpiBuilder, pda::permission_pda_from_permissioned_account, types::{Member, MemberFlags, MembersArgs}, }; -use ephemeral_spl_api::{require, require_eq_keys}; +use ephemeral_spl_api::{require, require_eq_keys, PodView}; use ephemeral_spl_api::{ require_n_accounts, state::{ephemeral_ata::EphemeralAta, load_initialized}, @@ -23,7 +23,7 @@ use pinocchio::{error::ProgramError, AccountView, ProgramResult}; /// 3: [] - Builtin : System program. /// 4: [] - Program : Permission program (ACL). /// -/// Instruction Data: CreateEphemeralAtaPermission +/// Instruction Data: CreateEphemeralAtaPermissionArgs /// #[inline(always)] pub fn process_create_ephemeral_ata_permission( @@ -38,7 +38,7 @@ pub fn process_create_ephemeral_ata_permission( permission_program, ] = require_n_accounts!(accounts, 5); - let args = CreateEphemeralAtaPermission::try_from_bytes(instruction_data)?; + let args = CreateEphemeralAtaPermissionArgs::try_view_from(instruction_data)?; require!( payer_info.is_signer(), @@ -54,7 +54,7 @@ pub fn process_create_ephemeral_ata_permission( let ephemeral_ata = load_initialized::(unsafe { ephemeral_ata_info.borrow_unchecked() })?; - let flag_byte = args.flag_byte(); + let flag_byte = args.flag_byte; // Valid in 2 cases: // - Payer is the owner of the eata @@ -103,33 +103,8 @@ pub fn process_create_ephemeral_ata_permission( .invoke() } -/// -/// DataLayout: -/// -/// 00..01 : flag_byte (u8) -/// -/// ValidLength: -/// -/// >= 01 -/// -pub struct CreateEphemeralAtaPermission<'a> { - raw: *const u8, - _data: PhantomData<&'a [u8]>, -} - -impl CreateEphemeralAtaPermission<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!(!bytes.is_empty(), ProgramError::InvalidInstructionData); - - Ok(CreateEphemeralAtaPermission { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn flag_byte(&self) -> u8 { - unsafe { *self.raw } - } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct CreateEphemeralAtaPermissionArgs { + flag_byte: u8, } diff --git a/e-token/src/processor/delegate_ephemeral_ata.rs b/e-token/src/processor/delegate_ephemeral_ata.rs index e88d041..024c114 100644 --- a/e-token/src/processor/delegate_ephemeral_ata.rs +++ b/e-token/src/processor/delegate_ephemeral_ata.rs @@ -1,8 +1,9 @@ +use data_layout::fixed_layout; use ephemeral_rollups_pinocchio::instruction::DelegateAccountCpiBuilder; use ephemeral_rollups_pinocchio::types::DelegateConfig; use ephemeral_spl_api::require_n_accounts; use ephemeral_spl_api::state::{ephemeral_ata::EphemeralAta, load_initialized}; -use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; +use pinocchio::{AccountView, Address, ProgramResult}; /// /// Executes on: @@ -35,7 +36,7 @@ pub fn process_delegate_ephemeral_ata( system_program, ] = require_n_accounts!(accounts, 8); - let args = DelegateArgs::try_from_bytes(instruction_data)?; + let args = DelegateArgs::try_view_from(instruction_data)?; let delegation_program = ephemeral_spl_api::program::DELEGATION_PROGRAM_ID; if ephemeral_ata_info.owned_by(&delegation_program) { @@ -47,7 +48,9 @@ pub fn process_delegate_ephemeral_ata( load_initialized::(unsafe { ephemeral_ata_info.borrow_unchecked() })?; let config = DelegateConfig { - validator: args.validator().map(Address::new_from_array), + validator: args + .validator() + .map(|slice| Address::new_from_array(*slice)), ..DelegateConfig::default() }; @@ -77,37 +80,7 @@ pub fn process_delegate_ephemeral_ata( .invoke() } -/// -/// DataLayout: -/// -/// 00..32 : validator (optional [u8; 32]) -/// -/// ValidLength: -/// -/// 00 | 32 -/// +#[fixed_layout] pub struct DelegateArgs { - validator: Option<[u8; 32]>, -} - -impl DelegateArgs { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.is_empty() { - Ok(DelegateArgs { validator: None }) - } else if bytes.len() == 32 { - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes[..32]); - Ok(DelegateArgs { - validator: Some(arr), - }) - } else { - Err(ProgramError::InvalidInstructionData) - } - } - - #[inline] - pub fn validator(&self) -> Option<[u8; 32]> { - self.validator - } + pub validator: Option<[u8; 32]>, } diff --git a/e-token/src/processor/delegate_shuttle_ephemeral_ata.rs b/e-token/src/processor/delegate_shuttle_ephemeral_ata.rs index a0c0414..e624531 100644 --- a/e-token/src/processor/delegate_shuttle_ephemeral_ata.rs +++ b/e-token/src/processor/delegate_shuttle_ephemeral_ata.rs @@ -1,3 +1,4 @@ +use data_layout::fixed_layout; use ephemeral_rollups_pinocchio::instruction::DelegateAccountCpiBuilder; use ephemeral_rollups_pinocchio::types::DelegateConfig; use ephemeral_spl_api::state::{ @@ -39,7 +40,7 @@ pub fn process_delegate_shuttle_ephemeral_ata( system_program, ] = require_n_accounts!(accounts, 9); - let args = DelegateShuttleArgs::try_from_bytes(instruction_data)?; + let args = DelegateShuttleArgs::try_view_from(instruction_data)?; let delegation_program = ephemeral_spl_api::program::DELEGATION_PROGRAM_ID; if ephemeral_ata_info.owned_by(&delegation_program) { @@ -82,7 +83,9 @@ pub fn process_delegate_shuttle_ephemeral_ata( let seeds: &[&[u8]] = &[shuttle_info.address().as_ref(), mint.as_ref()]; let config = DelegateConfig { - validator: args.validator().map(Address::new_from_array), + validator: args + .validator() + .map(|slice| Address::new_from_array(*slice)), ..DelegateConfig::default() }; @@ -106,37 +109,7 @@ pub fn process_delegate_shuttle_ephemeral_ata( .invoke() } -/// -/// DataLayout: -/// -/// 00..32 : validator (optional [u8; 32]) -/// -/// ValidLength: -/// -/// 00 | >= 32 -/// +#[fixed_layout] pub struct DelegateShuttleArgs { - validator: Option<[u8; 32]>, -} - -impl DelegateShuttleArgs { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.is_empty() { - Ok(DelegateShuttleArgs { validator: None }) - } else if bytes.len() >= 32 { - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes[..32]); - Ok(DelegateShuttleArgs { - validator: Some(arr), - }) - } else { - Err(ProgramError::InvalidInstructionData) - } - } - - #[inline] - pub fn validator(&self) -> Option<[u8; 32]> { - self.validator - } + pub validator: Option<[u8; 32]>, } diff --git a/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs b/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs index bd035cf..678b719 100644 --- a/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs +++ b/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs @@ -70,7 +70,7 @@ pub fn process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge( vault_token_info, ] = require_n_accounts!(accounts, 19); - let args = DepositAndDelegateShuttleArgs::try_from_bytes(instruction_data)?; + let args = DepositAndDelegateShuttleArgs::try_view_from(instruction_data)?; let accounts = DepositAndDelegateShuttleWithMergeAccounts { common: DepositAndDelegateShuttleAccounts { diff --git a/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs b/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs index a73f8c8..a6ba32f 100644 --- a/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs +++ b/e-token/src/processor/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs @@ -1,15 +1,14 @@ #[cfg(feature = "logging")] use alloc::string::ToString; use alloc::vec::Vec; -use core::marker::PhantomData; -use core::ptr::read_unaligned; -use core::slice; +use data_layout::fixed_layout; use dlp_api::args::{ EncryptedBuffer, MaybeEncryptedAccountMeta, MaybeEncryptedInstruction, MaybeEncryptedIxData, MaybeEncryptedPubkey, }; use dlp_api::compact::{self}; +use ephemeral_spl_api::instruction::ESplInstruction; use ephemeral_spl_api::state::transfer_queue::{queue_views, TransferQueue}; use ephemeral_spl_api::{require, require_eq_keys, require_n_accounts}; use pinocchio::{error::ProgramError, AccountView, ProgramResult}; @@ -83,7 +82,7 @@ pub fn process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private queue_info, ] = require_n_accounts!(accounts, 19); - let args = DepositAndDelegateShuttleWithPrivateTransferArgs::try_from_bytes(instruction_data)?; + let args = DepositAndDelegateShuttleWithPrivateTransferArgs::try_view_from(instruction_data)?; require!(args.amount() != 0, ProgramError::InvalidInstructionData); let common_accounts = DepositAndDelegateShuttleAccounts { @@ -189,131 +188,31 @@ pub fn process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private ) } -/// -/// DataLayout: -/// -/// 00..04 : shuttle_id (u32) -/// 04..12 : amount (u64) -/// 12.... : validator ([len:u8; PubkeyData]; 0 => None, 32 => Some(Pubkey)) -/// ...... : encrypted_destination [len:u8; buffer: &u[8]] -/// ...... : encrypted_data_suffix [len:u8; buffer: &u[8]] -/// -/// `.....` means variable-length data, including empty/optional segments when len = 0. -/// -struct DepositAndDelegateShuttleWithPrivateTransferArgs<'a> { - raw: *const u8, - len: usize, - _data: PhantomData<&'a [u8]>, +#[fixed_layout] +pub struct DepositAndDelegateShuttleWithPrivateTransferArgs { + pub shuttle_id: u32, + pub amount: u64, + pub validator: Option<[u8; 32]>, + #[capacity = 100] + pub encrypted_destination: Vec, + #[capacity = 120] + pub encrypted_data_suffix: Vec, } -impl DepositAndDelegateShuttleWithPrivateTransferArgs<'_> { - #[inline] - fn try_from_bytes( - bytes: &[u8], - ) -> Result, ProgramError> { - // MIN_LEN is the sum of the legth of 3 mandatory fixed-size fields - // - const MIN_LEN: usize = 4 + // shuttle_id - 8 + // amount - 1 + // min len for optional validator - 1 + 32 + // min len for mandatory destination_ata - 1 + 8 + 8 + 4 // min len for mandatory (min_delay_ms + max_delay_ms + split) - ; - - require!(bytes.len() >= MIN_LEN, ProgramError::InvalidInstructionData); - - Ok(DepositAndDelegateShuttleWithPrivateTransferArgs { - raw: bytes.as_ptr(), - len: bytes.len(), - _data: PhantomData, - }) - } - - #[inline] - fn shuttle_id(&self) -> u32 { - let mut buf = [0u8; 4]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 4); - } - u32::from_le_bytes(buf) - } - - #[inline] - fn amount(&self) -> u64 { - unsafe { read_unaligned(self.raw.add(4) as *const u64) } - } - - #[inline] - fn common_args(&self) -> Result { +impl DepositAndDelegateShuttleWithPrivateTransferArgsView<'_> { + fn common_args(&self) -> Result, ProgramError> { Ok(DepositAndDelegateShuttleCommonArgs { shuttle_id: self.shuttle_id(), amount: self.amount(), - validator: self.validator()?, + validator: self.validator(), }) } - - #[inline] - fn validator(&self) -> Result, ProgramError> { - let data = unsafe { self.read_vardata::<0>()? }; - - if data.is_empty() { - return Ok(None); - } - require!(data.len() == 32, ProgramError::InvalidInstructionData); - - let mut validator = [0u8; 32]; - unsafe { - core::ptr::copy_nonoverlapping(data.as_ptr(), validator.as_mut_ptr(), 32); - } - Ok(Some(validator)) - } - - // decrypted { destination_owner: pubkey } - #[inline] - fn encrypted_destination(&self) -> Result<&[u8], ProgramError> { - unsafe { self.read_vardata::<1>() } - } - - // decrypted { min_delay_ms: u64, max_delay_ms: u64, split: u32, client_ref_id?: u64 } :: PACKED - // Legacy payloads may still append flags before client_ref_id; the inner - // DepositAndQueueTransfer parser keeps that layout for backward compatibility. - #[inline] - fn encrypted_data_suffix(&self) -> Result<&[u8], ProgramError> { - unsafe { self.read_vardata::<2>() } - } - - unsafe fn read_vardata(&self) -> Result<&[u8], ProgramError> { - let mut offset = 12; // index where first vardata starts - let mut var = 0; - while var < VARINDEX { - require!(offset < self.len, ProgramError::InvalidInstructionData); - - let len = *self.raw.add(offset); - - offset += 1 + len as usize; - var += 1; - } - require!(offset < self.len, ProgramError::InvalidInstructionData); - - let len = *self.raw.add(offset); - - if len == 0 { - Ok(&[]) - } else if (offset + 1 + len as usize) <= self.len { - Ok(slice::from_raw_parts( - self.raw.add(offset + 1), - len as usize, - )) - } else { - Err(ProgramError::InvalidInstructionData) - } - } } fn private_transfer_action_encrypted( common_accounts: &DepositAndDelegateShuttleAccounts<'_>, queue_info: &AccountView, - args: &DepositAndDelegateShuttleWithPrivateTransferArgs<'_>, + args: &DepositAndDelegateShuttleWithPrivateTransferArgsView<'_>, amount: u64, ) -> Result { Ok(PostDelegationActions { @@ -330,7 +229,7 @@ fn private_transfer_action_encrypted( ), // 5 MaybeEncryptedPubkey::ClearText(common_accounts.vault_token_info.address().to_bytes()), // 6 MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( - args.encrypted_destination()?.into() + args.encrypted_destination().into() )), // 7 MaybeEncryptedPubkey::ClearText( common_accounts.token_program_info.address().to_bytes() @@ -353,13 +252,8 @@ fn private_transfer_action_encrypted( MaybeEncryptedAccountMeta::ClearText(compact::AccountMeta::new(9, false)), // shuttle_wallet_ata_info ], data: MaybeEncryptedIxData { - prefix: { - let mut data_prefix = Vec::with_capacity(1 + 8); - data_prefix.push(ephemeral_spl_api::instruction::DEPOSIT_AND_QUEUE_TRANSFER); - data_prefix.extend_from_slice(&amount.to_le_bytes()); - data_prefix - }, - suffix: EncryptedBuffer::new(args.encrypted_data_suffix()?.into()), + prefix: ESplInstruction::DepositAndQueueTransfer.with_data(&amount.to_le_bytes()), + suffix: EncryptedBuffer::new(args.encrypted_data_suffix().into()), }, }], }) diff --git a/e-token/src/processor/deposit_and_queue_transfer.rs b/e-token/src/processor/deposit_and_queue_transfer.rs index 1b21387..ce97399 100644 --- a/e-token/src/processor/deposit_and_queue_transfer.rs +++ b/e-token/src/processor/deposit_and_queue_transfer.rs @@ -1,9 +1,10 @@ -use core::{convert::TryFrom, marker::PhantomData}; +use core::convert::TryFrom; use crate::processor::internal::token_vault::transfer_to_vault_for_mint; use crate::processor::utils::{ get_associated_token_address, read_mint_decimals, validate_token_account, }; +use data_layout::fixed_layout; #[cfg(feature = "logging")] use ephemeral_spl_api::state::transfer_queue::queue_peek_next_task_id_from_data; use ephemeral_spl_api::state::transfer_queue::{ @@ -54,7 +55,8 @@ pub fn process_deposit_and_queue_transfer( reimbursement_token_info, ] = require_n_accounts!(accounts, 9); - let args = DepositAndQueueTransferArgs::try_from_bytes(instruction_data)?; + pinocchio_log::log!("instruction_data: {}", instruction_data.len()); + let args = DepositAndQueueTransferArgs::try_view_from(instruction_data)?; require!( user_authority.is_signer(), @@ -183,7 +185,7 @@ pub fn process_deposit_and_queue_transfer( ready_at, client_ref_id, task_id: 0, - flags: args.flags() | QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA, + flags: args.flags().unwrap_or(0) | QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA, group_id: [0; 3], }; queued_transfer.set_group_id(group_id)?; @@ -217,111 +219,14 @@ pub fn process_deposit_and_queue_transfer( Ok(()) } -/// -/// DataLayout: -/// -/// 00..08 : amount (u64) -/// 08..16 : min_delay_ms (u64) -/// 16..24 : max_delay_ms (u64) -/// 24..28 : split (u32) -/// 28..29 : flags (optional u8) -/// -/// ValidLength: -/// -/// 28 | 29 -/// -pub struct DepositAndQueueTransferArgs<'a> { - raw: *const u8, - len: usize, - _data: PhantomData<&'a [u8]>, -} - -impl DepositAndQueueTransferArgs<'_> { - const LEN: usize = 28; - const LEN_WITH_FLAGS: usize = 29; - const LEN_WITH_CLIENT_REF_ID: usize = 36; - const LEN_WITH_FLAGS_AND_CLIENT_REF_ID: usize = 37; - - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!( - bytes.len() == Self::LEN - || bytes.len() == Self::LEN_WITH_FLAGS - || bytes.len() == Self::LEN_WITH_CLIENT_REF_ID - || bytes.len() == Self::LEN_WITH_FLAGS_AND_CLIENT_REF_ID, - ProgramError::InvalidInstructionData - ); - - require!( - !matches!( - bytes.len(), - Self::LEN_WITH_FLAGS | Self::LEN_WITH_FLAGS_AND_CLIENT_REF_ID - ) || (bytes[Self::LEN] & !QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA) == 0, - ProgramError::InvalidInstructionData - ); - - Ok(DepositAndQueueTransferArgs { - raw: bytes.as_ptr(), - len: bytes.len(), - _data: PhantomData, - }) - } - - #[inline] - pub fn amount(&self) -> u64 { - self.read_u64(0) - } - - #[inline] - pub fn min_delay_ms(&self) -> u64 { - self.read_u64(8) - } - - #[inline] - pub fn max_delay_ms(&self) -> u64 { - self.read_u64(16) - } - - #[inline] - pub fn split(&self) -> u32 { - let mut buf = [0u8; 4]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(24), buf.as_mut_ptr(), 4); - } - u32::from_le_bytes(buf) - } - - #[inline] - pub fn flags(&self) -> u8 { - // TODO: Remove legacy flags parsing once all writers emit the - // client_ref_id suffix layout without flags. - if matches!( - self.len, - Self::LEN_WITH_FLAGS | Self::LEN_WITH_FLAGS_AND_CLIENT_REF_ID - ) { - unsafe { *self.raw.add(Self::LEN) } - } else { - 0 - } - } - - #[inline] - pub fn client_ref_id(&self) -> Option { - match self.len { - Self::LEN_WITH_CLIENT_REF_ID => Some(self.read_u64(Self::LEN)), - Self::LEN_WITH_FLAGS_AND_CLIENT_REF_ID => Some(self.read_u64(Self::LEN_WITH_FLAGS)), - _ => None, - } - } - - #[inline] - fn read_u64(&self, offset: usize) -> u64 { - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(offset), buf.as_mut_ptr(), 8); - } - u64::from_le_bytes(buf) - } +#[fixed_layout] +pub struct DepositAndQueueTransferArgs { + pub amount: u64, + pub min_delay_ms: u64, + pub max_delay_ms: u64, + pub split: u32, + pub flags: Option, + pub client_ref_id: Option, } #[inline(always)] @@ -512,40 +417,3 @@ fn hash_delay_seed(destination: &ephemeral_spl_api::Address, queue_position: u64 hash = hash.wrapping_mul(0x100_0000_01b3); hash ^ (hash >> 32) } - -#[cfg(test)] -mod tests { - use super::*; - - fn valid_base_bytes() -> [u8; DepositAndQueueTransferArgs::LEN] { - let mut bytes = [0u8; DepositAndQueueTransferArgs::LEN]; - bytes[..8].copy_from_slice(&1_u64.to_le_bytes()); - bytes[8..16].copy_from_slice(&2_u64.to_le_bytes()); - bytes[16..24].copy_from_slice(&3_u64.to_le_bytes()); - bytes[24..28].copy_from_slice(&1_u32.to_le_bytes()); - bytes - } - - #[test] - fn deposit_and_queue_transfer_args_accept_client_ref_id_without_flags() { - let mut bytes = [0u8; DepositAndQueueTransferArgs::LEN_WITH_CLIENT_REF_ID]; - bytes[..DepositAndQueueTransferArgs::LEN].copy_from_slice(&valid_base_bytes()); - bytes[DepositAndQueueTransferArgs::LEN..].copy_from_slice(&42_u64.to_le_bytes()); - - let args = DepositAndQueueTransferArgs::try_from_bytes(&bytes).unwrap(); - assert_eq!(args.flags(), 0); - assert_eq!(args.client_ref_id(), Some(42)); - } - - #[test] - fn deposit_and_queue_transfer_args_accept_legacy_flags_before_client_ref_id() { - let mut bytes = [0u8; DepositAndQueueTransferArgs::LEN_WITH_FLAGS_AND_CLIENT_REF_ID]; - bytes[..DepositAndQueueTransferArgs::LEN].copy_from_slice(&valid_base_bytes()); - bytes[DepositAndQueueTransferArgs::LEN] = QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA; - bytes[DepositAndQueueTransferArgs::LEN_WITH_FLAGS..].copy_from_slice(&77_u64.to_le_bytes()); - - let args = DepositAndQueueTransferArgs::try_from_bytes(&bytes).unwrap(); - assert_eq!(args.flags(), QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA); - assert_eq!(args.client_ref_id(), Some(77)); - } -} diff --git a/e-token/src/processor/deposit_spl_tokens.rs b/e-token/src/processor/deposit_spl_tokens.rs index d033d7e..07979cc 100644 --- a/e-token/src/processor/deposit_spl_tokens.rs +++ b/e-token/src/processor/deposit_spl_tokens.rs @@ -1,7 +1,7 @@ -use core::marker::PhantomData; +use bytemuck::{Pod, Zeroable}; use ephemeral_spl_api::state::{load_initialized, load_mut_initialized}; -use ephemeral_spl_api::{require, require_n_accounts}; +use ephemeral_spl_api::{require, require_n_accounts, PodView}; use { ephemeral_spl_api::state::ephemeral_ata::EphemeralAta, @@ -40,7 +40,7 @@ pub fn process_deposit_spl_tokens( token_program_info, ] = require_n_accounts!(accounts, 7); - let args = DepositArgs::try_from_bytes(instruction_data)?; + let args = DepositArgs::try_view_from(instruction_data)?; // Validate EphemeralAta ownership first, before reading raw data. require!( @@ -62,50 +62,21 @@ pub fn process_deposit_spl_tokens( user_authority, token_program_info, &ephemeral_ata_mint, - args.amount(), + args.amount, )?; let ephemeral_ata = load_mut_initialized::(unsafe { ephemeral_ata_info.borrow_unchecked_mut() })?; ephemeral_ata.amount = ephemeral_ata .amount - .checked_add(args.amount()) + .checked_add(args.amount) .ok_or(ProgramError::InvalidArgument)?; Ok(()) } -/// -/// DataLayout: -/// -/// 00..08 : amount (u64) -/// -/// ValidLength: -/// -/// >= 08 -/// -pub struct DepositArgs<'a> { - raw: *const u8, - _data: PhantomData<&'a [u8]>, -} - -impl DepositArgs<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!(bytes.len() >= 8, ProgramError::InvalidInstructionData); - Ok(DepositArgs { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn amount(&self) -> u64 { - // read LE u64 from bytes[0..8] - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 8); - } - u64::from_le_bytes(buf) - } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct DepositArgs { + amount: u64, } diff --git a/e-token/src/processor/ensure_transfer_queue_crank.rs b/e-token/src/processor/ensure_transfer_queue_crank.rs index aa46363..45cf6bd 100644 --- a/e-token/src/processor/ensure_transfer_queue_crank.rs +++ b/e-token/src/processor/ensure_transfer_queue_crank.rs @@ -4,7 +4,6 @@ use dlp_api::pda::magic_fee_vault_pda_from_validator; use ephemeral_rollups_pinocchio::crank::{ CancelCrankCpi, CrankInstruction, ScheduleCrankArgs, ScheduleCrankCpi, }; -use ephemeral_spl_api::instruction::internal::PROCESS_TRANSFER_QUEUE_TICK; use ephemeral_spl_api::state::transfer_queue::{ queue_crank_task_id_from_data, queue_set_crank_task_id_from_data, queue_views_checked, TransferQueue, @@ -14,12 +13,14 @@ use pinocchio::cpi::{invoke_signed_with_bounds, Signer}; use pinocchio::instruction::{InstructionAccount, InstructionView}; use pinocchio::{error::ProgramError, AccountView, ProgramResult}; +use crate::instruction::ESplInternalInstruction; + pub const CRANK_EXECUTION_INTERVAL_MILLIS: i64 = 500; const PROCESS_QUEUE_TICK_CRANK_ACCOUNTS: usize = 4; const SCHEDULE_CRANK_CPI_ACCOUNTS: usize = 5; const SCHEDULE_CRANK_DATA_LEN: usize = - 4 + 8 + 8 + 8 + 8 + 32 + 8 + (PROCESS_QUEUE_TICK_CRANK_ACCOUNTS * 34) + 8 + 1; + 4 + 8 + 8 + 8 + 8 + 32 + 8 + (PROCESS_QUEUE_TICK_CRANK_ACCOUNTS * 34) + 8 + 8; /// /// Executes on: @@ -113,7 +114,7 @@ pub fn process_ensure_transfer_queue_crank( .invoke_signed(&queue_signers)?; } - let tick_data = [PROCESS_TRANSFER_QUEUE_TICK]; + let tick_data = ESplInternalInstruction::ProcessTransferQueueTick.to_bytes(); let tick_accounts = [ InstructionAccount { address: queue_info.address(), diff --git a/e-token/src/processor/execute_ready_queued_transfer.rs b/e-token/src/processor/execute_ready_queued_transfer.rs index 4b78092..43222f9 100644 --- a/e-token/src/processor/execute_ready_queued_transfer.rs +++ b/e-token/src/processor/execute_ready_queued_transfer.rs @@ -1,5 +1,3 @@ -use core::marker::PhantomData; - use { ephemeral_rollups_pinocchio::pda::ephemeral_balance_pda_from_payer, ephemeral_spl_api::state::{ @@ -14,6 +12,7 @@ use crate::processor::{ internal::token_vault::validate_vault_for_mint, utils::read_mint_decimals, }; +use data_layout::fixed_layout; use pinocchio::cpi::{Seed, Signer}; use pinocchio_system::ID as SYSTEM_PROGRAM_ID; @@ -57,7 +56,7 @@ pub fn process_execute_ready_queued_transfer( escrow_signer, ] = require_n_accounts!(accounts, 12); - let args = ExecuteQueuedTransferArgs::try_from_bytes(instruction_data)?; + let args = ExecuteQueuedTransferArgs::try_view_from(instruction_data)?; // Note that accounts [source_program, escrow_authority, escrow_signer] are appended by DLP's // CallHandlerV2 instruction. @@ -142,75 +141,72 @@ pub fn process_execute_ready_queued_transfer( Ok(()) } -/// -/// DataLayout: -/// -/// 00..01 : escrow_index (u8) -/// 01..09 : amount (u64) -/// 09..10 : flags (u8) -/// -/// ValidLength: -/// -/// 10 -/// -pub struct ExecuteQueuedTransferArgs<'a> { - raw: *const u8, - len: usize, - _data: PhantomData<&'a [u8]>, +#[fixed_layout] +pub struct ExecuteQueuedTransferArgs { + pub amount: u64, + pub client_ref_id: Option, + pub escrow_index: u8, + pub flags: u8, } -impl ExecuteQueuedTransferArgs<'_> { - const LEN: usize = 10; - const LEN_WITH_CLIENT_REF_ID: usize = 18; - - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!( - bytes.len() == Self::LEN || bytes.len() == Self::LEN_WITH_CLIENT_REF_ID, - ProgramError::InvalidInstructionData - ); - - Ok(ExecuteQueuedTransferArgs { - raw: bytes.as_ptr(), - len: bytes.len(), - _data: PhantomData, - }) - } - - #[inline] - pub fn amount(&self) -> u64 { - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(1), buf.as_mut_ptr(), 8); - } - u64::from_le_bytes(buf) - } - - #[inline] - pub fn escrow_index(&self) -> u8 { - unsafe { *self.raw } - } - - #[inline] - pub fn flags(&self) -> u8 { - unsafe { *self.raw.add(9) } - } - - #[inline] +impl ExecuteQueuedTransferArgsView<'_> { pub fn should_create_destination_ata_idempotent(&self) -> bool { self.flags() & QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA != 0 } - - #[inline] - pub fn client_ref_id(&self) -> Option { - if self.len != Self::LEN_WITH_CLIENT_REF_ID { - return None; - } - - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(10), buf.as_mut_ptr(), 8); - } - Some(u64::from_le_bytes(buf)) - } } + +// impl ExecuteQueuedTransferArgs<'_> { +// const LEN: usize = 10; +// const LEN_WITH_CLIENT_REF_ID: usize = 18; +// +// #[inline] +// pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { +// require!( +// bytes.len() == Self::LEN || bytes.len() == Self::LEN_WITH_CLIENT_REF_ID, +// ProgramError::InvalidInstructionData +// ); +// +// Ok(ExecuteQueuedTransferArgs { +// raw: bytes.as_ptr(), +// len: bytes.len(), +// _data: PhantomData, +// }) +// } +// +// #[inline] +// pub fn amount(&self) -> u64 { +// let mut buf = [0u8; 8]; +// unsafe { +// core::ptr::copy_nonoverlapping(self.raw.add(1), buf.as_mut_ptr(), 8); +// } +// u64::from_le_bytes(buf) +// } +// +// #[inline] +// pub fn escrow_index(&self) -> u8 { +// unsafe { *self.raw } +// } +// +// #[inline] +// pub fn flags(&self) -> u8 { +// unsafe { *self.raw.add(9) } +// } +// +// #[inline] +// pub fn should_create_destination_ata_idempotent(&self) -> bool { +// self.flags() & QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA != 0 +// } +// +// #[inline] +// pub fn client_ref_id(&self) -> Option { +// if self.len != Self::LEN_WITH_CLIENT_REF_ID { +// return None; +// } +// +// let mut buf = [0u8; 8]; +// unsafe { +// core::ptr::copy_nonoverlapping(self.raw.add(10), buf.as_mut_ptr(), 8); +// } +// Some(u64::from_le_bytes(buf)) +// } +// } diff --git a/e-token/src/processor/initialize_shuttle_ephemeral_ata.rs b/e-token/src/processor/initialize_shuttle_ephemeral_ata.rs index ac9772d..7d6accb 100644 --- a/e-token/src/processor/initialize_shuttle_ephemeral_ata.rs +++ b/e-token/src/processor/initialize_shuttle_ephemeral_ata.rs @@ -1,5 +1,5 @@ -use core::marker::PhantomData; -use ephemeral_spl_api::{require, require_n_accounts}; +use bytemuck::{Pod, Zeroable}; +use ephemeral_spl_api::{require, require_n_accounts, PodView}; use pinocchio::{error::ProgramError, AccountView, ProgramResult}; use crate::processor::internal::ephemeral_ata::initialize_shuttle_ephemeral_ata_with_sponsor; @@ -19,7 +19,7 @@ use crate::processor::internal::ephemeral_ata::initialize_shuttle_ephemeral_ata_ /// 7: [] - SPL : Associated token program. /// 8: [] - Builtin : System program. /// -/// Instruction Data: InitializeShuttleEphemeralAta +/// Instruction Data: InitializeShuttleEphemeralAtaArgs /// #[inline(always)] pub fn process_initialize_shuttle_ephemeral_ata( @@ -38,7 +38,7 @@ pub fn process_initialize_shuttle_ephemeral_ata( system_program_info, ] = require_n_accounts!(accounts, 9); - let args = InitializeShuttleEphemeralAta::try_from_bytes(instruction_data)?; + let args = InitializeShuttleEphemeralAtaArgs::try_view_from(instruction_data)?; require!( payer_info.is_signer(), @@ -56,43 +56,14 @@ pub fn process_initialize_shuttle_ephemeral_ata( mint_info, token_program_info, system_program_info, - args.shuttle_id(), + args.shuttle_id, )?; Ok(()) } -/// -/// DataLayout: -/// -/// 00..04 : shuttle_id (u32) -/// -/// ValidLength: -/// -/// >= 04 -/// -pub struct InitializeShuttleEphemeralAta<'a> { - raw: *const u8, - _data: PhantomData<&'a [u8]>, -} - -impl InitializeShuttleEphemeralAta<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!(bytes.len() >= 4, ProgramError::InvalidInstructionData); - - Ok(InitializeShuttleEphemeralAta { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn shuttle_id(&self) -> u32 { - let mut buf = [0u8; 4]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 4); - } - u32::from_le_bytes(buf) - } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct InitializeShuttleEphemeralAtaArgs { + shuttle_id: u32, } diff --git a/e-token/src/processor/initialize_transfer_queue.rs b/e-token/src/processor/initialize_transfer_queue.rs index ebd88d6..a912279 100644 --- a/e-token/src/processor/initialize_transfer_queue.rs +++ b/e-token/src/processor/initialize_transfer_queue.rs @@ -1,4 +1,4 @@ -use core::marker::PhantomData; +use data_layout::fixed_layout; use ephemeral_rollups_pinocchio::acl::{ consts::PERMISSION_PROGRAM_ID, instruction::CreatePermissionCpiBuilder, pda::permission_pda_from_permissioned_account, types::MembersArgs, @@ -50,7 +50,7 @@ pub fn process_initialize_transfer_queue( permission_program_info, ] = require_n_accounts!(accounts, 7); - let args = InitializeTransferQueueArgs::try_from_bytes(instruction_data)?; + let args = InitializeTransferQueueArgs::try_view_from(instruction_data)?; let (requested_items, queue_size) = if let Some(items) = args.requested_items() { (items, HEADER_LEN + ITEM_LEN * items as usize) @@ -170,46 +170,7 @@ pub fn process_initialize_transfer_queue( Ok(()) } -/// -/// DataLayout: -/// -/// 00..04 : requested_items (optional u32) -/// -/// ValidLength: -/// -/// 00 | 04 -/// -pub struct InitializeTransferQueueArgs<'a> { - raw: *const u8, - len: usize, - _data: PhantomData<&'a [u8]>, -} - -impl InitializeTransferQueueArgs<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!( - bytes.is_empty() || bytes.len() == 4, - ProgramError::InvalidInstructionData - ); - - Ok(InitializeTransferQueueArgs { - raw: bytes.as_ptr(), - len: bytes.len(), - _data: PhantomData, - }) - } - - #[inline] - pub fn requested_items(&self) -> Option { - if self.len == 0 { - None - } else { - let mut buf = [0u8; 4]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 4); - } - Some(u32::from_le_bytes(buf)) - } - } +#[fixed_layout] +pub struct InitializeTransferQueueArgs { + pub requested_items: Option, } diff --git a/e-token/src/processor/internal/mod.rs b/e-token/src/processor/internal/mod.rs index e3cb1de..e893ad1 100644 --- a/e-token/src/processor/internal/mod.rs +++ b/e-token/src/processor/internal/mod.rs @@ -3,3 +3,5 @@ pub(crate) mod lamports_pda; pub(crate) mod shuttle_delegation; pub(crate) mod token_vault; pub(crate) mod transfer_queue_refill; + +pub use shuttle_delegation::DepositAndDelegateShuttleArgs; diff --git a/e-token/src/processor/internal/shuttle_delegation.rs b/e-token/src/processor/internal/shuttle_delegation.rs index 4c95f55..324d835 100644 --- a/e-token/src/processor/internal/shuttle_delegation.rs +++ b/e-token/src/processor/internal/shuttle_delegation.rs @@ -1,7 +1,8 @@ #[cfg(feature = "logging")] use alloc::string::ToString; +use data_layout::fixed_layout; -use core::{marker::PhantomData, mem::MaybeUninit}; +use core::mem::MaybeUninit; use dlp_api::args::PostDelegationActions; use ephemeral_rollups_pinocchio::{ consts::{ @@ -12,6 +13,7 @@ use ephemeral_rollups_pinocchio::{ types::{DelegateAccountArgs, DelegateConfig}, utils::{close_pda_acc, make_seed_buf}, }; +use ephemeral_spl_api::instruction::ESplInstruction; use ephemeral_spl_api::state::{ ephemeral_ata::EphemeralAta, load_initialized, load_mut_initialized, shuttle_ephemeral_ata::ShuttleMetadata, @@ -58,78 +60,22 @@ pub(crate) struct PreparedShuttleDelegation { } #[derive(Clone, Copy)] -pub(crate) struct DepositAndDelegateShuttleCommonArgs { +pub(crate) struct DepositAndDelegateShuttleCommonArgs<'a> { pub(crate) shuttle_id: u32, pub(crate) amount: u64, - pub(crate) validator: Option<[u8; 32]>, + pub(crate) validator: Option<&'a [u8; 32]>, } -/// -/// DataLayout: -/// -/// 00..04 : shuttle_id (u32) -/// 04..12 : amount (u64) -/// 12..44 : validator (optional [u8; 32]) -/// -/// ValidLength: -/// -/// 12 (without validator) -/// 44 (with validator) -/// -pub struct DepositAndDelegateShuttleArgs<'a> { - raw: *const u8, - len: usize, - _data: PhantomData<&'a [u8]>, +#[fixed_layout] +pub struct DepositAndDelegateShuttleArgs { + pub shuttle_id: u32, + pub amount: u64, + pub validator: Option<[u8; 32]>, } -impl DepositAndDelegateShuttleArgs<'_> { +impl DepositAndDelegateShuttleArgsView<'_> { #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!( - bytes.len() == 12 || bytes.len() == 44, - ProgramError::InvalidInstructionData - ); - - Ok(DepositAndDelegateShuttleArgs { - raw: bytes.as_ptr(), - len: bytes.len(), - _data: PhantomData, - }) - } - - #[inline] - pub fn shuttle_id(&self) -> u32 { - let mut buf = [0u8; 4]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 4); - } - u32::from_le_bytes(buf) - } - - #[inline] - pub fn amount(&self) -> u64 { - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(4), buf.as_mut_ptr(), 8); - } - u64::from_le_bytes(buf) - } - - #[inline] - pub fn validator(&self) -> Option<[u8; 32]> { - if self.len == 12 { - return None; - } - - let mut validator = [0u8; 32]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw.add(12), validator.as_mut_ptr(), 32); - } - Some(validator) - } - - #[inline] - pub(crate) fn common_args(&self) -> DepositAndDelegateShuttleCommonArgs { + pub(crate) fn common_args(&self) -> DepositAndDelegateShuttleCommonArgs<'_> { DepositAndDelegateShuttleCommonArgs { shuttle_id: self.shuttle_id(), amount: self.amount(), @@ -140,7 +86,7 @@ impl DepositAndDelegateShuttleArgs<'_> { pub(crate) fn process_deposit_and_delegate_shuttle_ephemeral_ata_with_post_actions( accounts: &DepositAndDelegateShuttleAccounts<'_>, - args: DepositAndDelegateShuttleCommonArgs, + args: DepositAndDelegateShuttleCommonArgs<'_>, extra_setup_lamports: u64, post_actions: PostDelegationActions, ) -> ProgramResult { @@ -366,7 +312,7 @@ pub(crate) fn delegate_sponsored_shuttle_with_post_actions( let seeds: &[&[u8]] = &[shuttle_info.address().as_ref(), mint.as_ref()]; let config = DelegateConfig { - validator: args.validator.map(Address::new_from_array), + validator: args.validator.map(|slice| Address::new_from_array(*slice)), ..DelegateConfig::default() }; @@ -412,7 +358,7 @@ pub(crate) fn merge_shuttle_into_token_account_action( AccountMeta::new_readonly(*accounts.mint_info.address(), false), AccountMeta::new_readonly(*accounts.token_program_info.address(), false), ], - data: alloc::vec![ephemeral_spl_api::instruction::MERGE_SHUTTLE_INTO_EPHEMERAL_ATA], + data: ESplInstruction::MergeShuttleIntoEphemeralAta.to_vec(), } } @@ -452,7 +398,7 @@ pub(crate) fn build_undelegate_and_close_shuttle_instruction( AccountMeta::new(Pubkey::from(MAGIC_CONTEXT_ID.to_bytes()), false), AccountMeta::new_readonly(Pubkey::from(MAGIC_PROGRAM_ID.to_bytes()), false), ], - data: alloc::vec![ephemeral_spl_api::instruction::UNDELEGATE_AND_CLOSE_SHUTTLE_TO_OWNER], + data: ESplInstruction::UndelegateAndCloseShuttleToOwner.to_vec(), } } diff --git a/e-token/src/processor/mod.rs b/e-token/src/processor/mod.rs index 4af6a05..c64d2cd 100644 --- a/e-token/src/processor/mod.rs +++ b/e-token/src/processor/mod.rs @@ -35,26 +35,39 @@ pub(crate) mod utils; pub mod withdraw_spl_tokens; pub mod withdraw_through_delegated_shuttle_with_merge; +pub use internal::DepositAndDelegateShuttleArgs; + pub use allocate_transfer_queue::process_allocate_transfer_queue; pub use close_ephemeral_ata::process_close_ephemeral_ata; pub use close_lamports_pda_intent::process_close_lamports_pda_intent; pub use close_shuttle_ata_intent::process_close_shuttle_ata_intent; pub use create_ephemeral_ata_permission::process_create_ephemeral_ata_permission; -pub use delegate_ephemeral_ata::process_delegate_ephemeral_ata; +pub use delegate_ephemeral_ata::{process_delegate_ephemeral_ata, DelegateArgs}; pub use delegate_ephemeral_ata_permission::process_delegate_ephemeral_ata_permission; -pub use delegate_shuttle_ephemeral_ata::process_delegate_shuttle_ephemeral_ata; +pub use delegate_shuttle_ephemeral_ata::{ + process_delegate_shuttle_ephemeral_ata, DelegateShuttleArgs, +}; pub use delegate_transfer_queue::process_delegate_transfer_queue; pub use deposit_and_delegate_shuttle_ephemeral_ata_with_merge::process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge; -pub use deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer::process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer; -pub use deposit_and_queue_transfer::process_deposit_and_queue_transfer; +pub use deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer::{ + process_deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer, + DepositAndDelegateShuttleWithPrivateTransferArgs, +}; +pub use deposit_and_queue_transfer::{ + process_deposit_and_queue_transfer, DepositAndQueueTransferArgs, +}; pub use deposit_spl_tokens::process_deposit_spl_tokens; pub use ensure_transfer_queue_crank::process_ensure_transfer_queue_crank; -pub use execute_ready_queued_transfer::process_execute_ready_queued_transfer; +pub use execute_ready_queued_transfer::{ + process_execute_ready_queued_transfer, ExecuteQueuedTransferArgs, +}; pub use initialize_ephemeral_ata::process_initialize_ephemeral_ata; pub use initialize_global_vault::process_initialize_global_vault; pub use initialize_rent_pda::process_initialize_rent_pda; pub use initialize_shuttle_ephemeral_ata::process_initialize_shuttle_ephemeral_ata; -pub use initialize_transfer_queue::process_initialize_transfer_queue; +pub use initialize_transfer_queue::{ + process_initialize_transfer_queue, InitializeTransferQueueArgs, +}; pub use mark_transfer_queue_refill_pending::process_mark_transfer_queue_refill_pending; pub use merge_shuttle_into_ephemeral_ata::process_merge_shuttle_into_ephemeral_ata; pub use pending_transfer_queue_refill::process_pending_transfer_queue_refill; diff --git a/e-token/src/processor/pending_transfer_queue_refill.rs b/e-token/src/processor/pending_transfer_queue_refill.rs index 09b0de1..2605da1 100644 --- a/e-token/src/processor/pending_transfer_queue_refill.rs +++ b/e-token/src/processor/pending_transfer_queue_refill.rs @@ -1,4 +1,4 @@ -use ephemeral_spl_api::instruction::SPONSORED_LAMPORTS_TRANSFER; +use ephemeral_spl_api::instruction::ESplInstruction; use ephemeral_spl_api::{require, require_eq_keys, require_n_accounts}; use pinocchio::cpi::{invoke_signed_with_bounds, Seed, Signer}; use pinocchio::instruction::{InstructionAccount, InstructionView}; @@ -15,7 +15,6 @@ use crate::processor::{ }; const SPONSORED_LAMPORTS_TRANSFER_CPI_ACCOUNTS: usize = 11; -const SPONSORED_LAMPORTS_TRANSFER_DATA_LEN: usize = 1 + 8 + 32; /// /// Executes on: @@ -116,10 +115,11 @@ fn trigger_queue_refill_via_sponsored_transfer( refill_lamports: u64, salt: &[u8; 32], ) -> ProgramResult { - let mut sponsored_transfer_data = [0_u8; SPONSORED_LAMPORTS_TRANSFER_DATA_LEN]; - sponsored_transfer_data[0] = SPONSORED_LAMPORTS_TRANSFER; - sponsored_transfer_data[1..9].copy_from_slice(&refill_lamports.to_le_bytes()); - sponsored_transfer_data[9..].copy_from_slice(salt); + let mut sponsored_transfer_payload = [0_u8; 40]; + sponsored_transfer_payload[..8].copy_from_slice(&refill_lamports.to_le_bytes()); + sponsored_transfer_payload[8..].copy_from_slice(salt); + let sponsored_transfer_data = + ESplInstruction::SponsoredLamportsTransfer.with_data(&sponsored_transfer_payload); let sponsored_transfer_accounts = [ InstructionAccount::readonly_signer(rent_pda_info.address()), diff --git a/e-token/src/processor/reset_ephemeral_ata_permission.rs b/e-token/src/processor/reset_ephemeral_ata_permission.rs index 666f0d1..ac5cb04 100644 --- a/e-token/src/processor/reset_ephemeral_ata_permission.rs +++ b/e-token/src/processor/reset_ephemeral_ata_permission.rs @@ -1,10 +1,10 @@ -use core::marker::PhantomData; +use bytemuck::{Pod, Zeroable}; use ephemeral_rollups_pinocchio::acl::{ consts::PERMISSION_PROGRAM_ID, instruction::UpdatePermissionCpiBuilder, types::{Member, MemberFlags, MembersArgs}, }; -use ephemeral_spl_api::{require, require_eq_keys}; +use ephemeral_spl_api::{require, require_eq_keys, PodView}; use ephemeral_spl_api::{ require_n_accounts, state::{ephemeral_ata::EphemeralAta, load_initialized}, @@ -21,7 +21,7 @@ use pinocchio::{error::ProgramError, AccountView, ProgramResult}; /// 2: [signer] - Keypair : Owner (must match the Ephemeral ATA owner). /// 3: [] - Program : Permission program (ACL). /// -/// Instruction Data: ResetEphemeralAtaPermission +/// Instruction Data: ResetEphemeralAtaPermissionArgs /// #[inline(always)] pub fn process_reset_ephemeral_ata_permission( @@ -35,7 +35,7 @@ pub fn process_reset_ephemeral_ata_permission( permission_program, ] = require_n_accounts!(accounts, 4); - let args = ResetEphemeralAtaPermission::try_from_bytes(instruction_data)?; + let args = ResetEphemeralAtaPermissionArgs::try_view_from(instruction_data)?; require!( owner_info.is_signer(), @@ -62,7 +62,7 @@ pub fn process_reset_ephemeral_ata_permission( ProgramError::InvalidAccountData ); - let mut members_flag = MemberFlags::from_acl_flag_byte(args.flag_byte()); + let mut members_flag = MemberFlags::from_acl_flag_byte(args.flag_byte); members_flag.set(MemberFlags::AUTHORITY); let members_buf = [Member { flags: members_flag, @@ -85,33 +85,8 @@ pub fn process_reset_ephemeral_ata_permission( .invoke() } -/// -/// DataLayout: -/// -/// 00..01 : flag_byte (u8) -/// -/// ValidLength: -/// -/// >= 01 -/// -pub struct ResetEphemeralAtaPermission<'a> { - raw: *const u8, - _data: PhantomData<&'a [u8]>, -} - -impl ResetEphemeralAtaPermission<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!(!bytes.is_empty(), ProgramError::InvalidInstructionData); - - Ok(ResetEphemeralAtaPermission { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn flag_byte(&self) -> u8 { - unsafe { *self.raw } - } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct ResetEphemeralAtaPermissionArgs { + flag_byte: u8, } diff --git a/e-token/src/processor/sponsored_lamports_transfer.rs b/e-token/src/processor/sponsored_lamports_transfer.rs index 2a3ffe7..400ad15 100644 --- a/e-token/src/processor/sponsored_lamports_transfer.rs +++ b/e-token/src/processor/sponsored_lamports_transfer.rs @@ -14,6 +14,7 @@ use pinocchio_system::instructions::{CreateAccount, Transfer}; use solana_instruction::{AccountMeta, Instruction}; use solana_pubkey::Pubkey; +use crate::instruction::ESplInternalInstruction; use crate::processor::{ initialize_rent_pda::{RENT_PDA, RENT_PDA_BUMP, RENT_PDA_SEED}, internal::lamports_pda::{derive_lamports_pda, parse_amount_and_salt, LAMPORTS_PDA_SEED}, @@ -191,9 +192,10 @@ fn transfer_lamports_pda_action( amount: u64, salt: &[u8; 32], ) -> Instruction { - let mut data = alloc::vec![ephemeral_spl_api::instruction::internal::TRANSFER_LAMPORTS_PDA]; - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(salt); + let mut payload = [0_u8; 40]; + payload[..8].copy_from_slice(&amount.to_le_bytes()); + payload[8..].copy_from_slice(salt); + let data = ESplInternalInstruction::TransferLamportsPda.with_data(&payload); Instruction { program_id: Pubkey::from(crate::ID), @@ -213,8 +215,7 @@ fn undelegate_lamports_pda_action( destination_info: &AccountView, salt: &[u8; 32], ) -> Instruction { - let mut data = alloc::vec![ephemeral_spl_api::instruction::internal::UNDELEGATE_LAMPORTS_PDA]; - data.extend_from_slice(salt); + let data = ESplInternalInstruction::UndelegateLamportsPda.with_data(salt); Instruction { program_id: Pubkey::from(crate::ID), diff --git a/e-token/src/processor/transfer_queue_tick.rs b/e-token/src/processor/transfer_queue_tick.rs index 23f40d7..cfc1add 100644 --- a/e-token/src/processor/transfer_queue_tick.rs +++ b/e-token/src/processor/transfer_queue_tick.rs @@ -6,13 +6,10 @@ use ephemeral_rollups_pinocchio::{ intent_bundle::{ActionArgs, CallHandler, MagicIntentBundleBuilder, ShortAccountMeta}, spl::consts::TOKEN_PROGRAM_ID, }; +use ephemeral_spl_api::require_n_accounts; use ephemeral_spl_api::state::transfer_queue::{ queue_peek_from_data, queue_pop_from_data, queue_views_checked, QueuedTransfer, QUEUE_SEED, }; -use ephemeral_spl_api::{ - instruction::internal::{EXECUTE_READY_QUEUED_TRANSFER, MARK_TRANSFER_QUEUE_REFILL_PENDING}, - require_n_accounts, -}; use ephemeral_spl_api::{require, require_eq_keys}; use pinocchio::cpi::{Seed, Signer}; use pinocchio::sysvars::{clock::Clock, Sysvar}; @@ -27,6 +24,10 @@ use crate::processor::{ MARK_TRANSFER_QUEUE_REFILL_PENDING_ESCROW_INDEX, }, }; +use crate::{ + instruction::ESplInternalInstruction, + processor::execute_ready_queued_transfer::ExecuteQueuedTransferArgs, +}; const EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX: u8 = 0; @@ -168,10 +169,8 @@ fn try_schedule_queue_refill( return Ok(false); } - let refill_data = [ - MARK_TRANSFER_QUEUE_REFILL_PENDING, - MARK_TRANSFER_QUEUE_REFILL_PENDING_ESCROW_INDEX, - ]; + let refill_data = ESplInternalInstruction::MarkTransferQueueRefillPending + .with_data(&[MARK_TRANSFER_QUEUE_REFILL_PENDING_ESCROW_INDEX]); let refill_accounts = [ ShortAccountMeta { pubkey: RENT_PDA, @@ -254,17 +253,18 @@ fn schedule_execute_ready_transfer( let destination_token_account = derive_associated_token_address(&queued_transfer.destination_owner, &queue_state.mint); - let mut execute_data = [0_u8; 19]; - execute_data[0] = EXECUTE_READY_QUEUED_TRANSFER; - execute_data[1] = EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX; - execute_data[2..10].copy_from_slice(&queued_transfer.amount.to_le_bytes()); - execute_data[10] = queued_transfer.flags; - let execute_data_len = if queued_transfer.client_ref_id != 0 { - execute_data[11..19].copy_from_slice(&queued_transfer.client_ref_id.to_le_bytes()); - 19 - } else { - 11 + let args = ExecuteQueuedTransferArgs { + amount: queued_transfer.amount, + client_ref_id: if queued_transfer.client_ref_id != 0 { + Some(queued_transfer.client_ref_id) + } else { + None + }, + escrow_index: EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX, + flags: queued_transfer.flags, }; + let execute_data = + ESplInternalInstruction::ExecuteReadyQueuedTransfer.with_data(&args.encode().unwrap()); // Note that we initialize CallHandler with 9 accounts only, and then 3 more accounts [source_program, // escrow_authority, escrow_signer] are appended by DLP's CallHandlerV2 instruction, which is @@ -310,7 +310,7 @@ fn schedule_execute_ready_transfer( let standalone_actions = [CallHandler { destination_program: crate::ID, escrow_authority: tick_accounts.queue_info.clone(), - args: ActionArgs::new(&execute_data[..execute_data_len]) + args: ActionArgs::new(&execute_data) .with_escrow_index(EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX), compute_units: EXECUTE_READY_QUEUED_TRANSFER_COMPUTE_UNITS, accounts: &execute_accounts, diff --git a/e-token/src/processor/undelegate_and_close_shuttle_to_owner.rs b/e-token/src/processor/undelegate_and_close_shuttle_to_owner.rs index 9c4e4a6..8f8aa61 100644 --- a/e-token/src/processor/undelegate_and_close_shuttle_to_owner.rs +++ b/e-token/src/processor/undelegate_and_close_shuttle_to_owner.rs @@ -2,13 +2,14 @@ use crate::processor::utils::{get_associated_token_address, validate_token_accou use ephemeral_rollups_pinocchio::intent_bundle::{ ActionArgs, CallHandler, MagicIntentBundleBuilder, ShortAccountMeta, }; -use ephemeral_spl_api::instruction::internal::SETTLE_AND_CLOSE_SHUTTLE_INTENT; use ephemeral_spl_api::state::{ ephemeral_ata::EphemeralAta, load_initialized, shuttle_ephemeral_ata::ShuttleMetadata, }; use ephemeral_spl_api::{require, require_eq_keys, require_n_accounts}; use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; +use crate::instruction::ESplInternalInstruction; + const DEFAULT_ESCROW_INDEX: u8 = u8::MAX; const INTENT_BUNDLE_DATA_BUF_SIZE: usize = 1536; const CLOSE_SHUTTLE_ATA_COMPUTE_UNITS: u32 = 100_000; @@ -159,7 +160,8 @@ fn schedule_shuttle_close_after_undelegate( ephemeral_spl_api::Address::find_program_address(&[mint.as_ref()], &crate::ID); let vault_token_info = get_associated_token_address(&vault_info, mint, token_program_info.address()); - let close_handler_data = [SETTLE_AND_CLOSE_SHUTTLE_INTENT, escrow_index]; + let close_handler_data = + ESplInternalInstruction::SettleAndCloseShuttleIntent.with_data(&[escrow_index]); let close_handler_accounts = [ ShortAccountMeta { pubkey: *rent_reimbursement.address(), diff --git a/e-token/src/processor/undelegate_lamports_pda.rs b/e-token/src/processor/undelegate_lamports_pda.rs index 218a234..7f74333 100644 --- a/e-token/src/processor/undelegate_lamports_pda.rs +++ b/e-token/src/processor/undelegate_lamports_pda.rs @@ -6,6 +6,7 @@ use pinocchio::sysvars::rent::Rent; use pinocchio::sysvars::Sysvar; use pinocchio::{error::ProgramError, AccountView, ProgramResult}; +use crate::instruction::ESplInternalInstruction; use crate::processor::internal::lamports_pda::derive_lamports_pda; const DEFAULT_ESCROW_INDEX: u8 = u8::MAX; @@ -108,12 +109,11 @@ pub fn process_undelegate_lamports_pda( .build_and_invoke(&mut intent_bundle_data) } -fn close_lamports_handler_data(escrow_index: u8, salt: &[u8; 32]) -> [u8; 34] { - let mut data = [0u8; 34]; - data[0] = ephemeral_spl_api::instruction::internal::CLOSE_LAMPORTS_PDA_INTENT; - data[1] = escrow_index; - data[2..].copy_from_slice(salt); - data +fn close_lamports_handler_data(escrow_index: u8, salt: &[u8; 32]) -> alloc::vec::Vec { + let mut payload = [0u8; 33]; + payload[0] = escrow_index; + payload[1..].copy_from_slice(salt); + ESplInternalInstruction::CloseLamportsPdaIntent.with_data(&payload) } fn parse_salt(instruction_data: &[u8]) -> Result<[u8; 32], ProgramError> { diff --git a/e-token/src/processor/withdraw_spl_tokens.rs b/e-token/src/processor/withdraw_spl_tokens.rs index a0a1209..ddf1d63 100644 --- a/e-token/src/processor/withdraw_spl_tokens.rs +++ b/e-token/src/processor/withdraw_spl_tokens.rs @@ -1,6 +1,6 @@ -use core::marker::PhantomData; -use ephemeral_spl_api::{require, require_n_accounts}; -use pinocchio::{error::ProgramError, AccountView, ProgramResult}; +use bytemuck::{Pod, Zeroable}; +use ephemeral_spl_api::{require_n_accounts, PodView}; +use pinocchio::{AccountView, ProgramResult}; use crate::processor::internal::token_vault::withdraw_ephemeral_ata_tokens; @@ -34,7 +34,7 @@ pub fn process_withdraw_spl_tokens( token_program_info, ] = require_n_accounts!(accounts, 7); - let args = WithdrawArgs::try_from_bytes(instruction_data)?; + let args = WithdrawArgs::try_view_from(instruction_data)?; withdraw_ephemeral_ata_tokens( owner, @@ -45,41 +45,12 @@ pub fn process_withdraw_spl_tokens( vault_source_token_acc, user_dest_token_acc, token_program_info, - args.amount(), + args.amount, ) } -/// -/// DataLayout: -/// -/// 00..08 : amount (u64) -/// -/// ValidLength: -/// -/// 08 -/// -pub struct WithdrawArgs<'a> { - raw: *const u8, - _data: PhantomData<&'a [u8]>, -} - -impl WithdrawArgs<'_> { - #[inline] - pub fn try_from_bytes(bytes: &[u8]) -> Result, ProgramError> { - require!(bytes.len() == 8, ProgramError::InvalidInstructionData); - Ok(WithdrawArgs { - raw: bytes.as_ptr(), - _data: PhantomData, - }) - } - - #[inline] - pub fn amount(&self) -> u64 { - // read LE u64 from bytes[0..8] - let mut buf = [0u8; 8]; - unsafe { - core::ptr::copy_nonoverlapping(self.raw, buf.as_mut_ptr(), 8); - } - u64::from_le_bytes(buf) - } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct WithdrawArgs { + amount: u64, } diff --git a/e-token/src/processor/withdraw_through_delegated_shuttle_with_merge.rs b/e-token/src/processor/withdraw_through_delegated_shuttle_with_merge.rs index 9bc7f85..946b121 100644 --- a/e-token/src/processor/withdraw_through_delegated_shuttle_with_merge.rs +++ b/e-token/src/processor/withdraw_through_delegated_shuttle_with_merge.rs @@ -83,7 +83,7 @@ pub fn process_withdraw_through_delegated_shuttle_with_merge( token_program_info, ] = require_n_accounts!(accounts, 16); - let args = DepositAndDelegateShuttleArgs::try_from_bytes(instruction_data)?; + let args = DepositAndDelegateShuttleArgs::try_view_from(instruction_data)?; let accounts = WithdrawThroughDelegatedShuttleAccounts { payer_info, diff --git a/e-token/tests/allocate_transfer_queue.rs b/e-token/tests/allocate_transfer_queue.rs index 1dbcab6..9677403 100644 --- a/e-token/tests/allocate_transfer_queue.rs +++ b/e-token/tests/allocate_transfer_queue.rs @@ -5,6 +5,7 @@ use ephemeral_spl_api::state::transfer_queue::{ queue_views_checked, TransferQueue, HEADER_LEN, ITEM_LEN, TRANSFER_QUEUE_VERSION, }; use ephemeral_spl_api::{instruction, ID as PROGRAM}; +use ephemeral_token_program::InitializeTransferQueueArgs; use solana_account::Account; use solana_instruction::{AccountMeta, Instruction}; use solana_keypair::Keypair; @@ -50,11 +51,13 @@ async fn allocate_transfer_queue_succeeds_and_is_idempotent() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: [ - vec![instruction::INITIALIZE_TRANSFER_QUEUE], - (N_ITEMS as u32).to_le_bytes().to_vec(), - ] - .concat(), + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: Some(N_ITEMS as u32), + } + .encode() + .unwrap(), + ), }; let tx_init = Transaction::new_signed_with_payer( @@ -75,7 +78,7 @@ async fn allocate_transfer_queue_succeeds_and_is_idempotent() { AccountMeta::new(queue, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::ALLOCATE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::AllocateTransferQueue.to_vec(), }; let mut previous_data_len = 0usize; diff --git a/e-token/tests/close_ephemeral_ata.rs b/e-token/tests/close_ephemeral_ata.rs index bd23e6d..c7f626f 100644 --- a/e-token/tests/close_ephemeral_ata.rs +++ b/e-token/tests/close_ephemeral_ata.rs @@ -43,7 +43,7 @@ async fn close_ephemeral_ata_refunds_rent_and_closes_account() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let tx_init = Transaction::new_signed_with_payer( @@ -82,7 +82,7 @@ async fn close_ephemeral_ata_refunds_rent_and_closes_account() { AccountMeta::new(ephemeral_ata, false), AccountMeta::new(recipient, false), ], - data: vec![instruction::CLOSE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::CloseEphemeralAta.to_vec(), }; let tx_close = Transaction::new_signed_with_payer( diff --git a/e-token/tests/create_ephemeral_ata_permission.rs b/e-token/tests/create_ephemeral_ata_permission.rs index 8cb03cd..ddb1369 100644 --- a/e-token/tests/create_ephemeral_ata_permission.rs +++ b/e-token/tests/create_ephemeral_ata_permission.rs @@ -34,7 +34,7 @@ async fn create_ephemeral_ata_permission() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let ix_create_permission = Instruction { @@ -49,7 +49,7 @@ async fn create_ephemeral_ata_permission() { data: { let flag = ephemeral_rollups_pinocchio::acl::types::MemberFlags::default().to_acl_flag_byte(); - vec![instruction::CREATE_EPHEMERAL_ATA_PERMISSION, flag] + instruction::ESplInstruction::CreateEphemeralAtaPermission.with_data(&[flag]) }, }; @@ -105,7 +105,7 @@ async fn create_ephemeral_ata_permission_permissionless_default() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let ix_create_permission = Instruction { @@ -120,7 +120,7 @@ async fn create_ephemeral_ata_permission_permissionless_default() { data: { let flag = ephemeral_rollups_pinocchio::acl::types::MemberFlags::default().to_acl_flag_byte(); - vec![instruction::CREATE_EPHEMERAL_ATA_PERMISSION, flag] + instruction::ESplInstruction::CreateEphemeralAtaPermission.with_data(&[flag]) }, }; diff --git a/e-token/tests/delegate_ephemeral_ata.rs b/e-token/tests/delegate_ephemeral_ata.rs index 89b2805..7005c56 100644 --- a/e-token/tests/delegate_ephemeral_ata.rs +++ b/e-token/tests/delegate_ephemeral_ata.rs @@ -5,6 +5,7 @@ use ephemeral_rollups_pinocchio::pda::{ use ephemeral_spl_api::state::RawType; use ephemeral_spl_api::ID as PROGRAM; use ephemeral_spl_api::{instruction, state::ephemeral_ata::EphemeralAta}; +use ephemeral_token_program::DelegateArgs; use solana_instruction::{AccountMeta, Instruction}; use solana_program_test::tokio; use solana_signer::Signer; @@ -39,7 +40,7 @@ async fn delegate_ephemeral_ata_succeeds() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let vault_token_acc = utils::derive_associated_token_address(pdas.vault, mint); @@ -57,7 +58,7 @@ async fn delegate_ephemeral_ata_succeeds() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), // associated token program AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx_init = Transaction::new_signed_with_payer( @@ -103,7 +104,8 @@ async fn delegate_ephemeral_ata_succeeds() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), // delegation program AccountMeta::new_readonly(solana_system_interface::program::ID, false), // system program ], - data: vec![instruction::DELEGATE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::DelegateEphemeralAta + .with_data(&DelegateArgs { validator: None }.encode().unwrap()), }; let tx = Transaction::new_signed_with_payer( @@ -156,7 +158,7 @@ async fn delegate_ephemeral_ata_succeeds() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let reinit_blockhash = context.banks_client.get_latest_blockhash().await.unwrap(); @@ -213,7 +215,7 @@ async fn delegate_ephemeral_ata_non_owner_succeeds() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let vault_token_acc = utils::derive_associated_token_address(pdas.vault, mint); @@ -231,7 +233,7 @@ async fn delegate_ephemeral_ata_non_owner_succeeds() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), // associated token program AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx_init = Transaction::new_signed_with_payer( @@ -264,7 +266,8 @@ async fn delegate_ephemeral_ata_non_owner_succeeds() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::DELEGATE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::DelegateEphemeralAta + .with_data(&DelegateArgs { validator: None }.encode().unwrap()), }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/delegate_ephemeral_ata_permission.rs b/e-token/tests/delegate_ephemeral_ata_permission.rs index 9d71d56..568a2c5 100644 --- a/e-token/tests/delegate_ephemeral_ata_permission.rs +++ b/e-token/tests/delegate_ephemeral_ata_permission.rs @@ -55,7 +55,7 @@ async fn delegate_ephemeral_ata_permission_succeeds() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let ix_create_permission = Instruction { @@ -70,7 +70,7 @@ async fn delegate_ephemeral_ata_permission_succeeds() { data: { let flag = ephemeral_rollups_pinocchio::acl::types::MemberFlags::default().to_acl_flag_byte(); - vec![instruction::CREATE_EPHEMERAL_ATA_PERMISSION, flag] + instruction::ESplInstruction::CreateEphemeralAtaPermission.with_data(&[flag]) }, }; @@ -107,7 +107,7 @@ async fn delegate_ephemeral_ata_permission_succeeds() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(validator, false), ], - data: vec![instruction::DELEGATE_EPHEMERAL_ATA_PERMISSION], + data: instruction::ESplInstruction::DelegateEphemeralAtaPermission.to_vec(), }; let tx_delegate = Transaction::new_signed_with_payer( @@ -188,7 +188,7 @@ async fn delegate_ephemeral_ata_permission_non_owner_succeeds() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let ix_create_permission = Instruction { @@ -203,7 +203,7 @@ async fn delegate_ephemeral_ata_permission_non_owner_succeeds() { data: { let flag = ephemeral_rollups_pinocchio::acl::types::MemberFlags::default().to_acl_flag_byte(); - vec![instruction::CREATE_EPHEMERAL_ATA_PERMISSION, flag] + instruction::ESplInstruction::CreateEphemeralAtaPermission.with_data(&[flag]) }, }; @@ -240,7 +240,7 @@ async fn delegate_ephemeral_ata_permission_non_owner_succeeds() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(validator, false), ], - data: vec![instruction::DELEGATE_EPHEMERAL_ATA_PERMISSION], + data: instruction::ESplInstruction::DelegateEphemeralAtaPermission.to_vec(), }; let tx_delegate = Transaction::new_signed_with_payer( diff --git a/e-token/tests/delegate_shuttle_ephemeral_ata.rs b/e-token/tests/delegate_shuttle_ephemeral_ata.rs index 862b141..539175c 100644 --- a/e-token/tests/delegate_shuttle_ephemeral_ata.rs +++ b/e-token/tests/delegate_shuttle_ephemeral_ata.rs @@ -6,6 +6,7 @@ use ephemeral_spl_api::state::ephemeral_ata::EphemeralAta; use ephemeral_spl_api::state::RawType; use ephemeral_spl_api::ID as PROGRAM; use ephemeral_spl_api::{instruction, state::shuttle_ephemeral_ata::ShuttleMetadata}; +use ephemeral_token_program::DelegateShuttleArgs; use solana_instruction::{AccountMeta, Instruction}; use solana_program_test::tokio; use solana_signer::Signer; @@ -32,7 +33,7 @@ async fn delegate_shuttle_ephemeral_ata_succeeds() { let (shuttle_eata, _) = EphemeralAta::find_pda(&shuttle_ephemeral_ata, &mint); let shuttle_wallet_ata = utils::derive_associated_token_address(shuttle_ephemeral_ata, mint); - let mut init_data = vec![instruction::INITIALIZE_SHUTTLE_EPHEMERAL_ATA]; + let mut init_data = instruction::ESplInstruction::InitializeShuttleEphemeralAta.to_vec(); init_data.extend_from_slice(&shuttle_id.to_le_bytes()); let ix_init_shuttle = Instruction { @@ -99,7 +100,8 @@ async fn delegate_shuttle_ephemeral_ata_succeeds() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::DELEGATE_SHUTTLE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::DelegateShuttleEphemeralAta + .with_data(&DelegateShuttleArgs { validator: None }.encode().unwrap()), }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/delegate_transfer_queue.rs b/e-token/tests/delegate_transfer_queue.rs index 16a06eb..b2b52ee 100644 --- a/e-token/tests/delegate_transfer_queue.rs +++ b/e-token/tests/delegate_transfer_queue.rs @@ -11,6 +11,7 @@ use ephemeral_spl_api::state::transfer_queue::{ TransferQueue, TransferQueueHeader, HEADER_LEN, TRANSFER_QUEUE_VERSION, }; use ephemeral_spl_api::ID as PROGRAM; +use ephemeral_token_program::InitializeTransferQueueArgs; use solana_account::Account; use solana_instruction::{AccountMeta, Instruction}; use solana_program_test::tokio; @@ -61,7 +62,13 @@ async fn delegate_transfer_queue_succeeds_and_is_idempotent() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let tx_init = Transaction::new_signed_with_payer( @@ -102,7 +109,7 @@ async fn delegate_transfer_queue_succeeds_and_is_idempotent() { AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::DELEGATE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::DelegateTransferQueue.to_vec(), }; let tx_delegate = Transaction::new_signed_with_payer( diff --git a/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs b/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs index 87a1962..2fe1318 100644 --- a/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs +++ b/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge.rs @@ -8,6 +8,7 @@ use ephemeral_spl_api::state::ephemeral_ata::EphemeralAta; use ephemeral_spl_api::state::load_initialized; use ephemeral_spl_api::state::shuttle_ephemeral_ata::ShuttleMetadata; use ephemeral_spl_api::ID as PROGRAM; +use ephemeral_token_program::DepositAndDelegateShuttleArgs; use solana_account::Account; use solana_instruction::{AccountMeta, Instruction}; use solana_program::rent::Rent; @@ -85,7 +86,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_deposits_and_stor AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = transfer(&payer, &rent_pda, 100_000_000); let ix_init_vault = Instruction { @@ -100,7 +101,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_deposits_and_stor AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let rent = context.banks_client.get_rent().await.unwrap(); let ix_create_owner_source = solana_system_interface::instruction::create_account( @@ -153,11 +154,6 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_deposits_and_stor let delegation_record_pda = delegation_record_pda_from_delegated_account(&shuttle_eata); let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&shuttle_eata); - let mut delegate_data = vec![instruction::SETUP_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE]; - delegate_data.extend_from_slice(&shuttle_id.to_le_bytes()); - delegate_data.extend_from_slice(&DEPOSIT_AMOUNT.to_le_bytes()); - delegate_data.extend_from_slice(&validator.to_bytes()); - let ix_delegate = Instruction { program_id: PROGRAM, accounts: vec![ @@ -181,7 +177,15 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_deposits_and_stor AccountMeta::new(owner_source_ata, false), AccountMeta::new(vault_ata, false), ], - data: delegate_data, + data: instruction::ESplInstruction::SetupAndDelegateShuttleEphemeralAtaWithMerge.with_data( + &DepositAndDelegateShuttleArgs { + shuttle_id, + amount: DEPOSIT_AMOUNT, + validator: Some(validator.to_bytes()), + } + .encode() + .unwrap(), + ), }; let tx_delegate = Transaction::new_signed_with_payer( diff --git a/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs b/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs index 4263ff2..7e88116 100644 --- a/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs +++ b/e-token/tests/deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_transfer.rs @@ -16,6 +16,10 @@ use ephemeral_spl_api::state::shuttle_ephemeral_ata::ShuttleMetadata; use ephemeral_spl_api::state::transfer_queue::{TransferQueue, TransferQueueHeader, HEADER_LEN}; use ephemeral_spl_api::state::{load, Initializable}; use ephemeral_spl_api::ID as PROGRAM; +use ephemeral_token_program::{ + DepositAndDelegateShuttleWithPrivateTransferArgs, DepositAndQueueTransferArgs, + InitializeTransferQueueArgs, +}; use solana_account::Account; use solana_instruction::{AccountMeta, Instruction}; use solana_program::rent::Rent; @@ -111,7 +115,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = transfer(&payer, &rent_pda, 100_000_000); let ix_init_vault = Instruction { @@ -126,7 +130,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let ix_init_queue = Instruction { program_id: PROGRAM, @@ -139,7 +143,13 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let rent = context.banks_client.get_rent().await.unwrap(); let ix_create_owner_source = solana_system_interface::instruction::create_account( @@ -204,50 +214,30 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans let delegation_record_pda = delegation_record_pda_from_delegated_account(&shuttle_eata); let delegation_metadata_pda = delegation_metadata_pda_from_delegated_account(&shuttle_eata); - let mut delegate_data = vec![ - instruction::DEPOSIT_AND_DELEGATE_SHUTTLE_EPHEMERAL_ATA_WITH_MERGE_AND_PRIVATE_TRANSFER, - ]; - delegate_data.extend_from_slice(&shuttle_id.to_le_bytes()); - delegate_data.extend_from_slice(&DEPOSIT_AMOUNT.to_le_bytes()); - - // add optional validator (varindex: 0) - { - delegate_data.push(32); - delegate_data.extend_from_slice(validator.as_array()); - } - - // add encrypted destination owner (varindex: 1) - { - let data = dlp_api::encryption::encrypt_ed25519_recipient( + let args = DepositAndDelegateShuttleWithPrivateTransferArgs { + shuttle_id, + amount: DEPOSIT_AMOUNT, + validator: Some(validator.as_array().to_owned()), + encrypted_destination: dlp_api::encryption::encrypt_ed25519_recipient( destination_owner.as_array(), &validator.to_bytes(), ) - .expect("validator key should be valid for encryption"); - - println!("encrypted_destination: {:?}", data); - - delegate_data.push(data.len().try_into().unwrap()); - delegate_data.extend_from_slice(&data); - } - - // add encrypted {min_delay_ms, max_delay_ms, split } (varindex: 2 - { - let data = dlp_api::encryption::encrypt_ed25519_recipient( - &[ - &MIN_DELAY_MS.to_le_bytes()[..], - &MAX_DELAY_MS.to_le_bytes()[..], - &SPLIT.to_le_bytes()[..], - ] - .concat(), + .expect("validator key should be valid for encryption"), + encrypted_data_suffix: dlp_api::encryption::encrypt_ed25519_recipient( + &DepositAndQueueTransferArgs { + amount: 0, // dont care its value + min_delay_ms: MIN_DELAY_MS, + max_delay_ms: MAX_DELAY_MS, + split: SPLIT, + flags: None, + client_ref_id: None, + } + .encode() + .unwrap()[8..], // except 'amount', encrypt everthing, amount will be prepended by ix. &validator.to_bytes(), ) - .expect("validator key should be valid for encryption"); - - println!("encrypted_data_suffix: {:?}", data); - - delegate_data.push(data.len().try_into().unwrap()); - delegate_data.extend_from_slice(&data); - } + .expect("validator key should be valid for encryption"), + }; let ix_delegate = Instruction { program_id: PROGRAM, @@ -272,7 +262,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans AccountMeta::new(vault_ata, false), AccountMeta::new(queue, false), ], - data: delegate_data, + data: instruction::ESplInstruction::DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer.with_data(&args.encode().unwrap()), }; let ix_delegate_queue = Instruction { @@ -288,7 +278,7 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans AccountMeta::new_readonly(ephemeral_rollups_pinocchio::ID, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::DELEGATE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::DelegateTransferQueue.to_vec(), }; let tx_delegate_queue = Transaction::new_signed_with_payer( @@ -410,7 +400,8 @@ async fn deposit_and_delegate_shuttle_ephemeral_ata_with_merge_and_private_trans let fee_amount = DEPOSIT_AMOUNT * PRIVATE_TRANSFER_FEE_BASIS_POINTS / BASIS_POINTS_DENOMINATOR; let private_transfer_amount = DEPOSIT_AMOUNT - fee_amount; - let mut private_transfer_prefix = vec![instruction::DEPOSIT_AND_QUEUE_TRANSFER]; + let mut private_transfer_prefix = + instruction::ESplInstruction::DepositAndQueueTransfer.to_vec(); private_transfer_prefix.extend_from_slice(&private_transfer_amount.to_le_bytes()); assert!( action_payload diff --git a/e-token/tests/deposit_and_queue_transfer.rs b/e-token/tests/deposit_and_queue_transfer.rs index ea48e0e..b09f44c 100644 --- a/e-token/tests/deposit_and_queue_transfer.rs +++ b/e-token/tests/deposit_and_queue_transfer.rs @@ -9,6 +9,7 @@ use ephemeral_spl_api::state::transfer_queue::{ queue_views_checked, QueuedTransfer, TransferQueue, TransferQueueHeader, HEADER_LEN, ITEM_LEN, }; use ephemeral_spl_api::ID as PROGRAM; +use ephemeral_token_program::{DepositAndQueueTransferArgs, InitializeTransferQueueArgs}; use solana_instruction::{AccountMeta, Instruction}; use solana_program::clock::Clock; use solana_program_pack::Pack; @@ -90,13 +91,17 @@ async fn setup_fixture(items: Option) -> Fixture { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; - let mut queue_init_data = vec![instruction::INITIALIZE_TRANSFER_QUEUE]; - if let Some(items) = items { - queue_init_data.extend_from_slice(&items.to_le_bytes()); - } + let queue_init_data = instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: items, + } + .encode() + .unwrap(), + ); + let ix_init_queue = Instruction { program_id: PROGRAM, accounts: vec![ @@ -198,17 +203,18 @@ fn build_deposit_and_queue_ix_for_destination( flags: Option, client_ref_id: Option, ) -> Instruction { - let mut data = vec![instruction::DEPOSIT_AND_QUEUE_TRANSFER]; - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(&min_delay_ms.to_le_bytes()); - data.extend_from_slice(&max_delay_ms.to_le_bytes()); - data.extend_from_slice(&split.to_le_bytes()); - if let Some(flags) = flags { - data.push(flags); - } - if let Some(client_ref_id) = client_ref_id { - data.extend_from_slice(&client_ref_id.to_le_bytes()); - } + let data = instruction::ESplInstruction::DepositAndQueueTransfer.with_data( + &DepositAndQueueTransferArgs { + amount, + min_delay_ms, + max_delay_ms, + split, + flags, + client_ref_id, + } + .encode() + .unwrap(), + ); Instruction { program_id: PROGRAM, @@ -858,11 +864,23 @@ async fn deposit_and_queue_transfer_return_to_shuttle() { let amount: u64 = 33_500_000; let split: u32 = 1000; let ix = { - let mut data = vec![instruction::DEPOSIT_AND_QUEUE_TRANSFER]; - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(&0_u64.to_le_bytes()); - data.extend_from_slice(&0_u64.to_le_bytes()); - data.extend_from_slice(&split.to_le_bytes()); + //let mut data = instruction::ESplInstruction::DepositAndQueueTransfer.to_vec(); + //data.extend_from_slice(&amount.to_le_bytes()); + //data.extend_from_slice(&0_u64.to_le_bytes()); + //data.extend_from_slice(&0_u64.to_le_bytes()); + //data.extend_from_slice(&split.to_le_bytes()); + let data = instruction::ESplInstruction::DepositAndQueueTransfer.with_data( + &DepositAndQueueTransferArgs { + amount, + min_delay_ms: 0, + max_delay_ms: 0, + split, + flags: None, + client_ref_id: None, + } + .encode() + .unwrap(), + ); Instruction { program_id: PROGRAM, diff --git a/e-token/tests/deposit_shuttle_spl_tokens.rs b/e-token/tests/deposit_shuttle_spl_tokens.rs index 12a412d..9efc4e4 100644 --- a/e-token/tests/deposit_shuttle_spl_tokens.rs +++ b/e-token/tests/deposit_shuttle_spl_tokens.rs @@ -46,7 +46,8 @@ async fn deposit_spl_tokens_increments_shuttle_amount() { let (vault_eata, _) = EphemeralAta::find_pda(&vault, &mint); let vault_ata = utils::derive_associated_token_address(vault, mint); - let mut shuttle_init_data = vec![instruction::INITIALIZE_SHUTTLE_EPHEMERAL_ATA]; + let mut shuttle_init_data = + instruction::ESplInstruction::InitializeShuttleEphemeralAta.to_vec(); shuttle_init_data.extend_from_slice(&shuttle_id.to_le_bytes()); let ix_init_shuttle = Instruction { program_id: PROGRAM, @@ -76,7 +77,7 @@ async fn deposit_spl_tokens_increments_shuttle_amount() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx_init = Transaction::new_signed_with_payer( @@ -92,7 +93,7 @@ async fn deposit_spl_tokens_increments_shuttle_amount() { .unwrap(); let amount: u64 = 100 * 10u64.pow(DECIMALS as u32); - let mut data = vec![instruction::DEPOSIT_SPL_TOKENS]; + let mut data = instruction::ESplInstruction::DepositSplTokens.to_vec(); data.extend_from_slice(&amount.to_le_bytes()); let ix_deposit = Instruction { diff --git a/e-token/tests/deposit_spl_tokens.rs b/e-token/tests/deposit_spl_tokens.rs index 4f5dd6a..5fe50df 100644 --- a/e-token/tests/deposit_spl_tokens.rs +++ b/e-token/tests/deposit_spl_tokens.rs @@ -62,7 +62,7 @@ async fn deposit_spl_tokens_increments_ephemeral_amount() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; // 2) Initialize Global Vault @@ -78,7 +78,7 @@ async fn deposit_spl_tokens_increments_ephemeral_amount() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), // associated token program AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; // Send both initializations in one tx @@ -105,7 +105,7 @@ async fn deposit_spl_tokens_increments_ephemeral_amount() { // 3) Deposit amount from payer's token to vault's token and increment Ephemeral ATA amount let amount: u64 = 100 * 10u64.pow(DECIMALS as u32); - let mut data = vec![instruction::DEPOSIT_SPL_TOKENS]; + let mut data = instruction::ESplInstruction::DepositSplTokens.to_vec(); data.extend_from_slice(&amount.to_le_bytes()); let ix_deposit = Instruction { diff --git a/e-token/tests/initialize_ephemeral_ata.rs b/e-token/tests/initialize_ephemeral_ata.rs index 8b3de3e..4253a37 100644 --- a/e-token/tests/initialize_ephemeral_ata.rs +++ b/e-token/tests/initialize_ephemeral_ata.rs @@ -32,7 +32,7 @@ async fn initialize_ephemeral_ata() { AccountMeta::new_readonly(mint, false), // mint seed (readonly) AccountMeta::new_readonly(solana_system_interface::program::ID, false), // system program (readonly) ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], // instruction data: discriminator + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), // instruction data: discriminator }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/initialize_global_vault.rs b/e-token/tests/initialize_global_vault.rs index 0a4a172..d9e39be 100644 --- a/e-token/tests/initialize_global_vault.rs +++ b/e-token/tests/initialize_global_vault.rs @@ -52,7 +52,7 @@ async fn initialize_global_vault() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), // associated token program AccountMeta::new_readonly(solana_system_interface::program::ID, false), // system program ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx = Transaction::new_signed_with_payer( @@ -148,7 +148,7 @@ async fn initialize_global_vault_migrates_legacy_layout() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/initialize_shuttle_ephemeral_ata.rs b/e-token/tests/initialize_shuttle_ephemeral_ata.rs index dd63d05..0bbceb0 100644 --- a/e-token/tests/initialize_shuttle_ephemeral_ata.rs +++ b/e-token/tests/initialize_shuttle_ephemeral_ata.rs @@ -33,7 +33,7 @@ async fn initialize_shuttle_ephemeral_ata() { let (shuttle_eata, _) = utils::derive_shuttle_eata(PROGRAM, shuttle_ephemeral_ata, mint); let shuttle_wallet_ata = utils::derive_associated_token_address(shuttle_ephemeral_ata, mint); - let mut data = vec![instruction::INITIALIZE_SHUTTLE_EPHEMERAL_ATA]; + let mut data = instruction::ESplInstruction::InitializeShuttleEphemeralAta.to_vec(); data.extend_from_slice(&shuttle_id.to_le_bytes()); let ix = Instruction { diff --git a/e-token/tests/initialize_transfer_queue.rs b/e-token/tests/initialize_transfer_queue.rs index e1c2e17..8efbce0 100644 --- a/e-token/tests/initialize_transfer_queue.rs +++ b/e-token/tests/initialize_transfer_queue.rs @@ -4,6 +4,7 @@ use ephemeral_spl_api::state::transfer_queue::{ TRANSFER_QUEUE_VERSION, }; use ephemeral_spl_api::ID as PROGRAM; +use ephemeral_token_program::InitializeTransferQueueArgs; use solana_account::Account as SolanaAccount; use solana_instruction::Instruction; use { @@ -56,7 +57,13 @@ async fn initialize_transfer_queue_default_size() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(utils::permission_program_id(), false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let tx = Transaction::new_signed_with_payer( @@ -119,8 +126,13 @@ async fn initialize_transfer_queue_custom_size_is_idempotent() { let queue_permission = permission_pda_from_permissioned_account(&queue); let items = 4_u32; - let mut data = vec![instruction::INITIALIZE_TRANSFER_QUEUE]; - data.extend_from_slice(&items.to_le_bytes()); + let data = instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: Some(items), + } + .encode() + .unwrap(), + ); let ix_init_custom = Instruction { program_id: PROGRAM, @@ -162,7 +174,13 @@ async fn initialize_transfer_queue_custom_size_is_idempotent() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(utils::permission_program_id(), false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let tx_noop = Transaction::new_signed_with_payer( &[ix_noop], diff --git a/e-token/tests/lamports_pda.rs b/e-token/tests/lamports_pda.rs index fb93391..6ea2864 100644 --- a/e-token/tests/lamports_pda.rs +++ b/e-token/tests/lamports_pda.rs @@ -4,9 +4,7 @@ use ephemeral_rollups_pinocchio::pda::{ delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account, }; use ephemeral_spl_api::{ - consts::SPONSORED_LAMPORTS_TRANSFER_SETUP_LAMPORTS, - instruction::{self, internal}, - ID as PROGRAM, + consts::SPONSORED_LAMPORTS_TRANSFER_SETUP_LAMPORTS, instruction, ID as PROGRAM, }; use solana_account::Account; use solana_instruction::{AccountMeta, Instruction}; @@ -17,6 +15,8 @@ use solana_signer::Signer; use solana_system_interface::instruction::transfer; use solana_transaction::Transaction; +use crate::utils::TestInternalInstruction; + mod common; mod utils; @@ -109,7 +109,7 @@ async fn sponsored_lamports_transfer_delegates_zero_data_pda_and_charges_fee() { AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = transfer(&payer, &rent_pda, 100_000_000); let tx_init = Transaction::new_signed_with_payer( @@ -131,7 +131,8 @@ async fn sponsored_lamports_transfer_delegates_zero_data_pda_and_charges_fee() { .unwrap() .expect("rent pda must exist"); - let mut sponsored_transfer_data = vec![instruction::SPONSORED_LAMPORTS_TRANSFER]; + let mut sponsored_transfer_data = + instruction::ESplInstruction::SponsoredLamportsTransfer.to_vec(); sponsored_transfer_data.extend_from_slice(&TRANSFER_AMOUNT.to_le_bytes()); sponsored_transfer_data.extend_from_slice(&SALT); @@ -270,7 +271,7 @@ async fn transfer_lamports_pda_moves_requested_lamports_to_destination() { .into(), ); - let mut transfer_lamports_data = vec![internal::TRANSFER_LAMPORTS_PDA]; + let mut transfer_lamports_data = TestInternalInstruction::TransferLamportsPda.to_vec(); transfer_lamports_data.extend_from_slice(&TRANSFER_AMOUNT.to_le_bytes()); transfer_lamports_data.extend_from_slice(&SALT); @@ -358,7 +359,7 @@ async fn transfer_lamports_pda_allows_extra_lamports_on_source() { .into(), ); - let mut transfer_lamports_data = vec![internal::TRANSFER_LAMPORTS_PDA]; + let mut transfer_lamports_data = TestInternalInstruction::TransferLamportsPda.to_vec(); transfer_lamports_data.extend_from_slice(&TRANSFER_AMOUNT.to_le_bytes()); transfer_lamports_data.extend_from_slice(&SALT); diff --git a/e-token/tests/merge_shuttle_into_ephemeral_ata.rs b/e-token/tests/merge_shuttle_into_ephemeral_ata.rs index f18dab7..2472adc 100644 --- a/e-token/tests/merge_shuttle_into_ephemeral_ata.rs +++ b/e-token/tests/merge_shuttle_into_ephemeral_ata.rs @@ -41,7 +41,8 @@ async fn merge_shuttle_into_ephemeral_ata_transfers_from_shuttle_ata_to_destinat .await; let destination_ata = setup.user_tokens[0]; - let mut shuttle_init_data = vec![instruction::INITIALIZE_SHUTTLE_EPHEMERAL_ATA]; + let mut shuttle_init_data = + instruction::ESplInstruction::InitializeShuttleEphemeralAta.to_vec(); shuttle_init_data.extend_from_slice(&shuttle_id.to_le_bytes()); let ix_init_shuttle = Instruction { program_id: PROGRAM, @@ -122,7 +123,7 @@ async fn merge_shuttle_into_ephemeral_ata_transfers_from_shuttle_ata_to_destinat AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(spl_token_interface::ID, false), ], - data: vec![instruction::MERGE_SHUTTLE_INTO_EPHEMERAL_ATA], + data: instruction::ESplInstruction::MergeShuttleIntoEphemeralAta.to_vec(), }; let tx_merge = Transaction::new_signed_with_payer( diff --git a/e-token/tests/rent_pda.rs b/e-token/tests/rent_pda.rs index 2ad9d0b..2ab623c 100644 --- a/e-token/tests/rent_pda.rs +++ b/e-token/tests/rent_pda.rs @@ -26,7 +26,7 @@ async fn initialize_rent_pda_is_idempotent() { AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/reset_ephemeral_ata_permission.rs b/e-token/tests/reset_ephemeral_ata_permission.rs index 164ae4f..b2f281a 100644 --- a/e-token/tests/reset_ephemeral_ata_permission.rs +++ b/e-token/tests/reset_ephemeral_ata_permission.rs @@ -51,7 +51,7 @@ async fn reset_ephemeral_ata_permission() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; let ix_create_permission = Instruction { @@ -66,7 +66,7 @@ async fn reset_ephemeral_ata_permission() { data: { let flag = ephemeral_rollups_pinocchio::acl::types::MemberFlags::default().to_acl_flag_byte(); - vec![instruction::CREATE_EPHEMERAL_ATA_PERMISSION, flag] + instruction::ESplInstruction::CreateEphemeralAtaPermission.with_data(&[flag]) }, }; @@ -79,7 +79,7 @@ async fn reset_ephemeral_ata_permission() { AccountMeta::new(payer, true), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::RESET_EPHEMERAL_ATA_PERMISSION, reset_flag], + data: instruction::ESplInstruction::ResetEphemeralAtaPermission.with_data(&[reset_flag]), }; let tx = Transaction::new_signed_with_payer( diff --git a/e-token/tests/transfer_queue_automation.rs b/e-token/tests/transfer_queue_automation.rs index cc7790c..c9faa6a 100644 --- a/e-token/tests/transfer_queue_automation.rs +++ b/e-token/tests/transfer_queue_automation.rs @@ -12,9 +12,9 @@ use ephemeral_spl_api::state::transfer_queue::{ QueuedTransfer, TransferQueueHeader, HEADER_LEN, ITEM_LEN, QUEUE_SEED, }; use ephemeral_spl_api::ID as PROGRAM; -use ephemeral_spl_api::{ - instruction::{self, internal}, - state::transfer_queue::TransferQueue, +use ephemeral_spl_api::{instruction, state::transfer_queue::TransferQueue}; +use ephemeral_token_program::{ + DepositAndQueueTransferArgs, ExecuteQueuedTransferArgs, InitializeTransferQueueArgs, }; use magicblock_magic_program_api::{ args::{MagicIntentBundleArgs, ScheduleTaskArgs}, @@ -29,6 +29,8 @@ use solana_program::{ use solana_program_pack::Pack; use spl_token_interface::state::Account; +use crate::utils::TestInternalInstruction; + use { solana_keypair::Keypair, solana_program_test::{processor, tokio, ProgramTest, ProgramTestContext}, @@ -374,7 +376,7 @@ async fn setup_fixture() -> Fixture { AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = solana_system_interface::instruction::transfer(&payer, &rent_pda, 100_000_000); @@ -390,7 +392,7 @@ async fn setup_fixture() -> Fixture { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let ix_init_queue = Instruction { @@ -404,7 +406,13 @@ async fn setup_fixture() -> Fixture { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let ix_init_destination_ata = Instruction { @@ -464,14 +472,18 @@ async fn enqueue_transfer_with_client_ref_id( client_ref_id: Option, entry_key: &str, ) { - let mut data = vec![instruction::DEPOSIT_AND_QUEUE_TRANSFER]; - data.extend_from_slice(&QUEUED_AMOUNT.to_le_bytes()); - data.extend_from_slice(&min_delay_ms.to_le_bytes()); - data.extend_from_slice(&min_delay_ms.to_le_bytes()); - data.extend_from_slice(&1_u32.to_le_bytes()); - if let Some(client_ref_id) = client_ref_id { - data.extend_from_slice(&client_ref_id.to_le_bytes()); - } + let data = instruction::ESplInstruction::DepositAndQueueTransfer.with_data( + &DepositAndQueueTransferArgs { + amount: QUEUED_AMOUNT, + min_delay_ms, + max_delay_ms: min_delay_ms, + split: 1, + flags: None, + client_ref_id, + } + .encode() + .unwrap(), + ); let ix = Instruction { program_id: PROGRAM, @@ -518,7 +530,7 @@ fn ensure_queue_crank_ix_with_magic_program( AccountMeta::new(fixture.magic_context, false), AccountMeta::new_readonly(magic_program, false), ], - data: vec![instruction::ENSURE_TRANSFER_QUEUE_CRANK], + data: instruction::ESplInstruction::EnsureTransferQueueCrank.to_vec(), } } @@ -538,7 +550,7 @@ fn process_queue_tick_ix_with_magic_program( AccountMeta::new(fixture.magic_context, false), AccountMeta::new_readonly(magic_program, false), ], - data: vec![internal::PROCESS_TRANSFER_QUEUE_TICK], + data: TestInternalInstruction::ProcessTransferQueueTick.to_vec(), } } @@ -649,7 +661,7 @@ async fn ensure_transfer_queue_crank_schedules_one_recurring_queue_crank() { ); assert_eq!( captured[0].args.instructions[0].data, - vec![internal::PROCESS_TRANSFER_QUEUE_TICK] + TestInternalInstruction::ProcessTransferQueueTick.to_vec() ); let queue_after_first_ensure = queue_account(&mut fixture.context, fixture.queue).await; let header_after_first_ensure = read_header_unaligned(&queue_after_first_ensure.data); @@ -779,7 +791,7 @@ async fn ensure_transfer_queue_crank_rejects_non_magic_program() { AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = solana_system_interface::instruction::transfer(&payer, &rent_pda, 100_000_000); @@ -795,7 +807,7 @@ async fn ensure_transfer_queue_crank_rejects_non_magic_program() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let ix_init_queue = Instruction { @@ -809,7 +821,13 @@ async fn ensure_transfer_queue_crank_rejects_non_magic_program() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let ix_init_destination_ata = Instruction { @@ -1002,7 +1020,7 @@ async fn process_transfer_queue_tick_rejects_non_magic_program() { AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = solana_system_interface::instruction::transfer(&payer, &rent_pda, 100_000_000); @@ -1018,7 +1036,7 @@ async fn process_transfer_queue_tick_rejects_non_magic_program() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let ix_init_queue = Instruction { @@ -1032,7 +1050,13 @@ async fn process_transfer_queue_tick_rejects_non_magic_program() { AccountMeta::new_readonly(solana_system_interface::program::ID, false), AccountMeta::new_readonly(PERMISSION_PROGRAM_ID, false), ], - data: vec![instruction::INITIALIZE_TRANSFER_QUEUE], + data: instruction::ESplInstruction::InitializeTransferQueue.with_data( + &InitializeTransferQueueArgs { + requested_items: None, + } + .encode() + .unwrap(), + ), }; let ix_init_destination_ata = Instruction { @@ -1232,13 +1256,16 @@ async fn recurring_queue_crank_executes_ready_transfer_via_magic_bundle() { standalone_action.args.escrow_index, EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX ); - let mut expected_action_data = vec![ - internal::EXECUTE_READY_QUEUED_TRANSFER, - EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX, - ]; - expected_action_data.extend_from_slice(&expected_amount.to_le_bytes()); - expected_action_data - .push(ephemeral_spl_api::state::transfer_queue::QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA); + + let args = ExecuteQueuedTransferArgs { + amount: expected_amount, + client_ref_id: None, + escrow_index: EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX, + flags: ephemeral_spl_api::state::transfer_queue::QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA, + }; + let expected_action_data = + TestInternalInstruction::ExecuteReadyQueuedTransfer.with_data(&args.encode().unwrap()); + assert_eq!(standalone_action.args.data, expected_action_data); assert_eq!(standalone_action.accounts.len(), 9); let rent_pda = Pubkey::find_program_address(&[RENT_PDA_SEED], &PROGRAM).0; @@ -1383,13 +1410,13 @@ async fn recurring_queue_crank_includes_client_ref_id_in_execute_action_when_pre transfer_action.destination_program.to_bytes(), PROGRAM.to_bytes() ); - let mut expected_action_data = vec![ - internal::EXECUTE_READY_QUEUED_TRANSFER, - EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX, - ]; - expected_action_data.extend_from_slice(&expected_amount.to_le_bytes()); - expected_action_data - .push(ephemeral_spl_api::state::transfer_queue::QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA); - expected_action_data.extend_from_slice(&42_u64.to_le_bytes()); + let args = ExecuteQueuedTransferArgs { + amount: expected_amount, + client_ref_id: Some(42), + escrow_index: EXECUTE_READY_QUEUED_TRANSFER_ESCROW_INDEX, + flags: ephemeral_spl_api::state::transfer_queue::QUEUED_TRANSFER_FLAG_CREATE_IDEMPOTENT_ATA, + }; + let expected_action_data = + TestInternalInstruction::ExecuteReadyQueuedTransfer.with_data(&args.encode().unwrap()); assert_eq!(transfer_action.args.data, expected_action_data); } diff --git a/e-token/tests/utils.rs b/e-token/tests/utils.rs index a93a7f3..5c5a736 100644 --- a/e-token/tests/utils.rs +++ b/e-token/tests/utils.rs @@ -24,6 +24,38 @@ use solana_transaction::Transaction; use spl_token_interface::instruction::{initialize_account, initialize_account3, initialize_mint}; use spl_token_interface::state::{Account as SplAccount, Mint}; +// this must be same as ESplInternalInstruction +#[repr(u8)] +pub enum TestInternalInstruction { + UndelegationCallback = 196, + + SettleAndCloseShuttleIntent = 201, + ExecuteReadyQueuedTransfer = 202, + ProcessTransferQueueTick = 203, + TransferLamportsPda = 204, + UndelegateLamportsPda = 205, + CloseLamportsPdaIntent = 206, + MarkTransferQueueRefillPending = 207, +} + +impl TestInternalInstruction { + pub const fn discriminator(self) -> u8 { + self as u8 + } + pub const fn to_bytes(self) -> [u8; 8] { + [self.discriminator(), 0, 0, 0, 0, 0, 0, 0] + } + pub fn to_vec(self) -> Vec { + self.to_bytes().to_vec() + } + pub fn with_data(self, instruction_data: &[u8]) -> Vec { + let mut data = Vec::with_capacity(8 + instruction_data.len()); + data.extend_from_slice(&self.to_bytes()); + data.extend_from_slice(instruction_data); + data + } +} + #[allow(dead_code)] pub struct Pdas { pub ephemeral_ata: Pubkey, diff --git a/e-token/tests/withdraw_spl_tokens.rs b/e-token/tests/withdraw_spl_tokens.rs index b06f881..596588d 100644 --- a/e-token/tests/withdraw_spl_tokens.rs +++ b/e-token/tests/withdraw_spl_tokens.rs @@ -53,7 +53,7 @@ async fn withdraw_spl_tokens_decrements_ephemeral_amount() { AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_EPHEMERAL_ATA], + data: instruction::ESplInstruction::InitializeEphemeralAta.to_vec(), }; // Initialize Global Vault @@ -69,7 +69,7 @@ async fn withdraw_spl_tokens_decrements_ephemeral_amount() { AccountMeta::new_readonly(utils::associated_token_program_id(), false), // associated token program AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_GLOBAL_VAULT], + data: instruction::ESplInstruction::InitializeGlobalVault.to_vec(), }; let tx_init = Transaction::new_signed_with_payer( @@ -86,7 +86,7 @@ async fn withdraw_spl_tokens_decrements_ephemeral_amount() { // Deposit first to fund the vault and set ephemeral amount let deposit_amount: u64 = 1_000 * 10u64.pow(DECIMALS as u32); - let mut deposit_data = vec![instruction::DEPOSIT_SPL_TOKENS]; + let mut deposit_data = instruction::ESplInstruction::DepositSplTokens.to_vec(); deposit_data.extend_from_slice(&deposit_amount.to_le_bytes()); let ix_deposit = Instruction { program_id: PROGRAM, @@ -115,7 +115,7 @@ async fn withdraw_spl_tokens_decrements_ephemeral_amount() { // Now withdraw a portion let withdraw_amount: u64 = 400 * 10u64.pow(DECIMALS as u32); - let mut withdraw_data = vec![instruction::WITHDRAW_SPL_TOKENS]; + let mut withdraw_data = instruction::ESplInstruction::WithdrawSplTokens.to_vec(); withdraw_data.extend_from_slice(&withdraw_amount.to_le_bytes()); let ix_withdraw = Instruction { diff --git a/e-token/tests/withdraw_through_delegated_shuttle_with_merge.rs b/e-token/tests/withdraw_through_delegated_shuttle_with_merge.rs index 43fb57c..8111971 100644 --- a/e-token/tests/withdraw_through_delegated_shuttle_with_merge.rs +++ b/e-token/tests/withdraw_through_delegated_shuttle_with_merge.rs @@ -5,13 +5,14 @@ use std::{ use dlp_api::state::DelegationRecord; use ephemeral_spl_api::{ - instruction::{self, internal}, + instruction, state::{ ephemeral_ata::EphemeralAta, load, load_mut, shuttle_ephemeral_ata::ShuttleMetadata, Initializable, RawType, }, }; use ephemeral_spl_api::{state::load_initialized, ID as PROGRAM}; +use ephemeral_token_program::DepositAndDelegateShuttleArgs; use magicblock_magic_program_api::{ args::{MagicIntentBundleArgs, UndelegateTypeArgs}, instruction::MagicBlockInstruction, @@ -30,6 +31,8 @@ use solana_system_interface::instruction::transfer; use solana_transaction::Transaction; use spl_token_interface::state::Account as SplAccount; +use crate::utils::TestInternalInstruction; + mod common; mod utils; @@ -180,7 +183,7 @@ async fn withdraw_through_delegated_shuttle_with_merge_stores_transfer_and_clean AccountMeta::new(rent_pda, false), AccountMeta::new_readonly(solana_system_interface::program::ID, false), ], - data: vec![instruction::INITIALIZE_RENT_PDA], + data: instruction::ESplInstruction::InitializeRentPda.to_vec(), }; let ix_fund_rent = transfer(&payer, &rent_pda, 100_000_000); let rent = context.banks_client.get_rent().await.unwrap(); @@ -248,11 +251,6 @@ async fn withdraw_through_delegated_shuttle_with_merge_stores_transfer_and_clean &ephemeral_spl_api::program::DELEGATION_PROGRAM_ID, ); - let mut withdraw_data = vec![instruction::WITHDRAW_THROUGH_DELEGATED_SHUTTLE_WITH_MERGE]; - withdraw_data.extend_from_slice(&shuttle_id.to_le_bytes()); - withdraw_data.extend_from_slice(&TRANSFER_AMOUNT.to_le_bytes()); - withdraw_data.extend_from_slice(&validator.to_bytes()); - let ix_withdraw = Instruction { program_id: PROGRAM, accounts: vec![ @@ -273,7 +271,15 @@ async fn withdraw_through_delegated_shuttle_with_merge_stores_transfer_and_clean AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(spl_token_interface::ID, false), ], - data: withdraw_data, + data: instruction::ESplInstruction::WithdrawThroughDelegatedShuttleWithMerge.with_data( + &DepositAndDelegateShuttleArgs { + shuttle_id, + amount: TRANSFER_AMOUNT, + validator: Some(validator.to_bytes()), + } + .encode() + .unwrap(), + ), }; let tx_withdraw = Transaction::new_signed_with_payer( @@ -479,7 +485,7 @@ async fn undelegate_and_close_shuttle_ephemeral_ata_schedules_close_action() { AccountMeta::new(magic_context, false), AccountMeta::new_readonly(magic_program, false), ], - data: vec![instruction::UNDELEGATE_AND_CLOSE_SHUTTLE_TO_OWNER], + data: instruction::ESplInstruction::UndelegateAndCloseShuttleToOwner.to_vec(), }; let tx_undelegate = Transaction::new_signed_with_payer( &[ix_undelegate], @@ -512,7 +518,17 @@ async fn undelegate_and_close_shuttle_ephemeral_ata_schedules_close_action() { let close_action = &base_actions[0]; assert_eq!( close_action.args.data, - vec![internal::SETTLE_AND_CLOSE_SHUTTLE_INTENT, u8::MAX] + vec![ + TestInternalInstruction::SettleAndCloseShuttleIntent.discriminator(), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + u8::MAX + ] ); assert_eq!(close_action.accounts.len(), 9); assert_eq!(