diff --git a/clients/typescript/test/delegation-security.test.ts b/clients/typescript/test/delegation-security.test.ts index 39800a3..cee621f 100644 --- a/clients/typescript/test/delegation-security.test.ts +++ b/clients/typescript/test/delegation-security.test.ts @@ -127,7 +127,7 @@ describe('Delegation Security', () => { nonce: 1n, }); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee, delegator: t.payerKeypair.address, @@ -139,7 +139,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('close SubscriptionAuthority kills all transfers', async () => { @@ -313,7 +312,7 @@ describe('Delegation Security', () => { nonce: 0n, }); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee, delegator: t.payerKeypair.address, @@ -325,7 +324,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); await t.timeTravel(Number(expiryTs) + 200); @@ -390,7 +388,7 @@ describe('Delegation Security', () => { nonce: 0n, }); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferRecurring({ delegatee, delegator: t.payerKeypair.address, @@ -402,7 +400,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); await t.timeTravel(Number(expiryTs) + 200); @@ -482,7 +479,7 @@ describe('Delegation Security', () => { ); const legitimateAta = await t.createAtaWithBalance(t.tokenMint, legitimateDelegatee.address, 0n); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee: legitimateDelegatee, delegator: t.payerKeypair.address, @@ -494,7 +491,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('wrong signer rejected on recurring delegation', async () => { @@ -558,7 +554,7 @@ describe('Delegation Security', () => { ); const legitimateAta = await t.createAtaWithBalance(t.tokenMint, legitimateDelegatee.address, 0n); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferRecurring({ delegatee: legitimateDelegatee, delegator: t.payerKeypair.address, @@ -570,7 +566,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('skipped periods do not accumulate allowance', async () => { @@ -636,7 +631,7 @@ describe('Delegation Security', () => { SUBSCRIPTIONS_ERROR__AMOUNT_EXCEEDS_PERIOD_LIMIT, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferRecurring({ delegatee, delegator: t.payerKeypair.address, @@ -648,7 +643,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('exceed per-period limit is blocked', async () => { @@ -724,7 +718,7 @@ describe('Delegation Security', () => { SUBSCRIPTIONS_ERROR__AMOUNT_EXCEEDS_PERIOD_LIMIT, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferRecurring({ delegatee, delegator: t.payerKeypair.address, @@ -736,7 +730,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('transfer before recurring start time is blocked', async () => { @@ -801,7 +794,7 @@ describe('Delegation Security', () => { await t.timeTravel(Number(startTs) + 60); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferRecurring({ delegatee, delegator: t.payerKeypair.address, @@ -813,7 +806,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('cross-type nonce collision: fixed then recurring same nonce', async () => { @@ -938,7 +930,7 @@ describe('Delegation Security', () => { }), ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee, delegator: subscriber.address, @@ -950,7 +942,6 @@ describe('Delegation Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('nonce collision is blocked', async () => { @@ -995,7 +986,7 @@ describe('Delegation Security', () => { SUBSCRIPTIONS_ERROR__DELEGATION_ALREADY_EXISTS, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .createFixedDelegation({ delegator: t.payerKeypair, tokenMint: t.tokenMint, @@ -1005,7 +996,6 @@ describe('Delegation Security', () => { expiryTs: currentTs + BigInt(ONE_HOUR_IN_SECONDS), }) .sendTransaction(); - expect(signature).toBeDefined(); }); }); diff --git a/clients/typescript/test/multi-wallet-scenarios.test.ts b/clients/typescript/test/multi-wallet-scenarios.test.ts index 28e98ed..870a187 100644 --- a/clients/typescript/test/multi-wallet-scenarios.test.ts +++ b/clients/typescript/test/multi-wallet-scenarios.test.ts @@ -128,7 +128,7 @@ describe('Multi-Wallet Scenarios', () => { SUBSCRIPTIONS_ERROR__SUBSCRIPTION_CANCELLED, ); - const sigA = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscribers[0].address, @@ -140,9 +140,8 @@ describe('Multi-Wallet Scenarios', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(sigA).toBeDefined(); - const sigB = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscribers[1].address, @@ -154,7 +153,6 @@ describe('Multi-Wallet Scenarios', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(sigB).toBeDefined(); // Time travel moved past C's grace period (~2 hours), advancing A and B // into a new billing period. amountPulledInPeriod resets, so only the @@ -218,7 +216,7 @@ describe('Multi-Wallet Scenarios', () => { const merchantAta = await t.createAtaWithBalance(t.tokenMint, t.payerKeypair.address, 0n); - const chargeSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscriber.address, @@ -230,7 +228,6 @@ describe('Multi-Wallet Scenarios', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(chargeSig).toBeDefined(); await t.client.subscriptions.instructions .closeSubscriptionAuthority({ @@ -290,7 +287,7 @@ describe('Multi-Wallet Scenarios', () => { nonce: 0n, }); - const newTransferSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee: trustedDelegatee, delegator: subscriber.address, @@ -302,7 +299,6 @@ describe('Multi-Wallet Scenarios', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(newTransferSig).toBeDefined(); }); test('multi-mint kill-switch isolation', async () => { @@ -402,7 +398,7 @@ describe('Multi-Wallet Scenarios', () => { SUBSCRIPTIONS_ERROR__INVALID_SUBSCRIPTION_AUTHORITY_PDA, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferFixed({ delegatee, delegator: t.payerKeypair.address, @@ -414,6 +410,5 @@ describe('Multi-Wallet Scenarios', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); }); diff --git a/clients/typescript/test/subscription-lifecycle.test.ts b/clients/typescript/test/subscription-lifecycle.test.ts index b994580..f60febf 100644 --- a/clients/typescript/test/subscription-lifecycle.test.ts +++ b/clients/typescript/test/subscription-lifecycle.test.ts @@ -128,13 +128,12 @@ describe('Subscription Lifecycle', () => { // 6. Time-travel past endTs, then delete the plan await t.timeTravel(Number(endTs) + 60); - const deleteSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .deletePlan({ owner: t.payerKeypair, planPda, }) .sendTransaction(); - expect(deleteSig).toBeDefined(); const planAfterDelete = await fetchMaybePlan(t.rpc, planPda); expect(planAfterDelete.exists).toBe(false); @@ -197,7 +196,7 @@ describe('Subscription Lifecycle', () => { const pullerAta = await t.createAtaWithBalance(t.tokenMint, puller.address, 0n); const pullAmount = 100_000n; - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: puller, delegator: subscriber.address, @@ -210,8 +209,6 @@ describe('Subscription Lifecycle', () => { }) .sendTransaction(); - expect(signature).toBeDefined(); - const balance = await t.rpc.getTokenAccountBalance(pullerAta).send(); expect(balance.value.amount).toBe(pullAmount.toString()); }); @@ -408,14 +405,13 @@ describe('Subscription Lifecycle', () => { expect(subAfterCancel.expiresAtTs).not.toBe(0n); // 7. Subscriber revokes delegation, getting rent back - const revokeSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .revokeSubscription({ authority: subscriber, subscriptionPda, planPda, }) .sendTransaction(); - expect(revokeSig).toBeDefined(); // Subscription account should be closed const subAfterRevoke = await fetchMaybeSubscriptionDelegation(t.rpc, subscriptionPda); diff --git a/clients/typescript/test/subscription-security.test.ts b/clients/typescript/test/subscription-security.test.ts index dd73946..69dd681 100644 --- a/clients/typescript/test/subscription-security.test.ts +++ b/clients/typescript/test/subscription-security.test.ts @@ -207,14 +207,13 @@ describe('Subscription Security', () => { await t.timeTravel(Number(subAfterCancel.expiresAtTs) + 60); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .revokeSubscription({ authority: subscriber, subscriptionPda, planPda, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('unauthorized puller is rejected', async () => { @@ -288,7 +287,7 @@ describe('Subscription Security', () => { ); const pullerAta = await t.createAtaWithBalance(t.tokenMint, authorizedPuller.address, 0n); - const pullerSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: authorizedPuller, delegator: subscriber.address, @@ -300,10 +299,9 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(pullerSig).toBeDefined(); const merchantAta = await t.createAtaWithBalance(t.tokenMint, t.payerKeypair.address, 0n); - const merchantSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscriber.address, @@ -315,7 +313,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(merchantSig).toBeDefined(); }); test('destination whitelist is enforced', async () => { @@ -390,7 +387,7 @@ describe('Subscription Security', () => { SUBSCRIPTIONS_ERROR__UNAUTHORIZED_DESTINATION, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscriber.address, @@ -402,7 +399,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('double subscription is blocked', async () => { @@ -586,7 +582,7 @@ describe('Subscription Security', () => { }) .sendTransaction(); - const graceSig = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscriber.address, @@ -598,7 +594,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(graceSig).toBeDefined(); const subData = (await fetchSubscriptionDelegation(t.rpc, subscriptionPda)).data; await t.timeTravel(Number(subData.expiresAtTs) + 60); @@ -725,13 +720,12 @@ describe('Subscription Security', () => { await t.timeTravel(Number(endTs) + 60); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .deletePlan({ owner: t.payerKeypair, planPda, }) .sendTransaction(); - expect(signature).toBeDefined(); const planAfter = await fetchMaybePlan(t.rpc, planPda); expect(planAfter.exists).toBe(false); @@ -1050,14 +1044,13 @@ describe('Subscription Security', () => { const subAfterCancel = (await fetchSubscriptionDelegation(t.rpc, subscriptionPda)).data; expect(subAfterCancel.expiresAtTs).not.toBe(0n); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .revokeSubscription({ authority: subscriber, subscriptionPda, planPda, }) .sendTransaction(); - expect(signature).toBeDefined(); const subAfterRevoke = await fetchMaybeSubscriptionDelegation(t.rpc, subscriptionPda); expect(subAfterRevoke.exists).toBe(false); @@ -1287,7 +1280,7 @@ describe('Subscription Security', () => { const pullerAAta = await t.createAtaWithBalance(t.tokenMint, pullerA.address, 0n); - const firstPull = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: pullerA, delegator: subscriber.address, @@ -1299,7 +1292,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(firstPull).toBeDefined(); await t.client.subscriptions.instructions .updatePlan({ @@ -1329,7 +1321,7 @@ describe('Subscription Security', () => { ); const pullerBAta = await t.createAtaWithBalance(t.tokenMint, pullerB.address, 0n); - const newPull = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: pullerB, delegator: subscriber.address, @@ -1341,7 +1333,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(newPull).toBeDefined(); }); test('cancel with wrong plan account fails', async () => { @@ -1424,14 +1415,13 @@ describe('Subscription Security', () => { SUBSCRIPTIONS_ERROR__SUBSCRIPTION_PLAN_MISMATCH, ); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .cancelSubscription({ subscriber, planPda: planA, subscriptionPda, }) .sendTransaction(); - expect(signature).toBeDefined(); }); test('plan end_ts expiry blocks subscription transfer', async () => { @@ -1488,7 +1478,7 @@ describe('Subscription Security', () => { const merchantAta = await t.createAtaWithBalance(t.tokenMint, t.payerKeypair.address, 0n); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .transferSubscription({ caller: t.payerKeypair, delegator: subscriber.address, @@ -1500,7 +1490,6 @@ describe('Subscription Security', () => { tokenProgram: t.tokenProgram, }) .sendTransaction(); - expect(signature).toBeDefined(); await t.timeTravel(Number(endTs) + 60); @@ -1586,14 +1575,13 @@ describe('Subscription Security', () => { const subData = (await fetchSubscriptionDelegation(t.rpc, subscriptionPda)).data; expect(subData.expiresAtTs).toBe(endTs + 1n); - const signature = await t.client.subscriptions.instructions + await t.client.subscriptions.instructions .revokeSubscription({ authority: subscriber, subscriptionPda, planPda, }) .sendTransaction(); - expect(signature).toBeDefined(); const subAfterRevoke = await fetchMaybeSubscriptionDelegation(t.rpc, subscriptionPda); expect(subAfterRevoke.exists).toBe(false); diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index 62b697c..7221814 100644 --- a/program/src/entrypoint.rs +++ b/program/src/entrypoint.rs @@ -11,7 +11,7 @@ use crate::instructions::{ entrypoint!(process_instruction); pub fn process_instruction( - program_id: &Address, + _program_id: &Address, accounts: &mut [AccountView], instruction_data: &[u8], ) -> ProgramResult { @@ -39,6 +39,6 @@ pub fn process_instruction( SubscriptionsInstruction::RevokeAbandonedDelegation => revoke_abandoned_delegation::process(accounts), SubscriptionsInstruction::RevokeAbandonedSubscription => revoke_abandoned_subscription::process(accounts), SubscriptionsInstruction::ReclaimExcessRent => reclaim_excess_rent::process(accounts), - SubscriptionsInstruction::EmitEvent => emit_event::process(program_id, accounts), + SubscriptionsInstruction::EmitEvent => emit_event::process(accounts), } } diff --git a/program/src/errors.rs b/program/src/errors.rs index 4cc941d..febc721 100644 --- a/program/src/errors.rs +++ b/program/src/errors.rs @@ -8,101 +8,6 @@ impl From for ProgramError { } } -#[cfg(test)] -impl TryFrom for SubscriptionsError { - type Error = u32; - - fn try_from(code: u32) -> Result { - match code { - // Generic errors (100-199) - 100 => Ok(Self::NotSigner), - 101 => Ok(Self::InvalidAddress), - 102 => Ok(Self::InvalidEscrowPda), - 103 => Ok(Self::InvalidSubscriptionAuthorityPda), - 104 => Ok(Self::NotSystemProgram), - 105 => Ok(Self::InvalidTokenProgram), - 106 => Ok(Self::InvalidToken2022MintAccountData), - 107 => Ok(Self::InvalidToken2022TokenAccountData), - 108 => Ok(Self::InvalidAssociatedTokenAccountDerivedAddress), - 109 => Ok(Self::InvalidTokenSplMintAccountData), - 110 => Ok(Self::InvalidTokenSplTokenAccountData), - 111 => Ok(Self::InvalidAccountData), - 112 => Ok(Self::InvalidInstructionData), - 113 => Ok(Self::NotEnoughAccountKeys), - 114 => Ok(Self::InvalidInstruction), - 115 => Ok(Self::ArithmeticOverflow), - 116 => Ok(Self::ArithmeticUnderflow), - 117 => Ok(Self::InvalidAccountDiscriminator), - 118 => Ok(Self::MintHasConfidentialTransfer), - 119 => Ok(Self::MintHasNonTransferable), - 120 => Ok(Self::MintHasPermanentDelegate), - 121 => Ok(Self::MintHasTransferHook), - 122 => Ok(Self::MintHasTransferFee), - 123 => Ok(Self::MintHasMintCloseAuthority), - 124 => Ok(Self::MintHasPausable), - 125 => Ok(Self::MintMismatch), - 126 => Ok(Self::InvalidDelegatePda), - 127 => Ok(Self::InvalidHeaderData), - 128 => Ok(Self::DelegationExpired), - 129 => Ok(Self::InvalidAmount), - 130 => Ok(Self::Unauthorized), - 131 => Ok(Self::AccountNotWritable), - 132 => Ok(Self::AtaOwnerMismatch), - 133 => Ok(Self::DelegationVersionMismatch), - 134 => Ok(Self::MigrationRequired), - 135 => Ok(Self::DelegationAlreadyExists), - 136 => Ok(Self::StaleSubscriptionAuthority), - 137 => Ok(Self::TransferHookTooManyAccounts), - 138 => Ok(Self::NoExcessLamports), - // Fixed delegation errors (300-399) - 300 => Ok(Self::AmountExceedsLimit), - 301 => Ok(Self::FixedDelegationExpiryInPast), - 302 => Ok(Self::FixedDelegationAmountZero), - // Recurring delegation errors (400-499) - 400 => Ok(Self::AmountExceedsPeriodLimit), - 401 => Ok(Self::PeriodNotElapsed), - 402 => Ok(Self::InvalidPeriodLength), - 403 => Ok(Self::InvalidPayerData), - 404 => Ok(Self::RecurringDelegationStartTimeInPast), - 405 => Ok(Self::RecurringDelegationStartTimeGreaterThanExpiry), - 406 => Ok(Self::RecurringDelegationAmountZero), - 407 => Ok(Self::DelegationNotStarted), - 408 => Ok(Self::RecurringDelegationStartOnLandingRequiresExpiry), - // Plan and subscription errors (500-599) - 500 => Ok(Self::PlanSunset), - 501 => Ok(Self::PlanExpired), - 502 => Ok(Self::InvalidPlanPda), - 503 => Ok(Self::InvalidSubscriptionPda), - 504 => Ok(Self::NotPlanOwner), - 505 => Ok(Self::SubscriptionPlanMismatch), - 506 => Ok(Self::UnauthorizedDestination), - 507 => Ok(Self::InvalidNumDestinations), - 508 => Ok(Self::SubscriptionCancelled), - 509 => Ok(Self::SubscriptionAlreadyCancelled), - 510 => Ok(Self::SubscriptionNotCancelled), - 511 => Ok(Self::InvalidEndTs), - 512 => Ok(Self::InvalidPlanStatus), - 513 => Ok(Self::PlanImmutableAfterSunset), - 514 => Ok(Self::SunsetRequiresEndTs), - 515 => Ok(Self::PlanNotExpired), - 516 => Ok(Self::PlanClosed), - 517 => Ok(Self::AlreadySubscribed), - 518 => Ok(Self::PlanAlreadyExists), - 519 => Ok(Self::PlanTermsMismatch), - 520 => Ok(Self::PlanEndTsCannotExtend), - 521 => Ok(Self::StaleSubscriptionApproval), - 522 => Ok(Self::StalePlanApproval), - // Event errors (600-699) - 600 => Ok(Self::InvalidEventAuthority), - 601 => Ok(Self::InvalidEventData), - 602 => Ok(Self::InvalidEventTag), - 603 => Ok(Self::InvalidEventDiscriminator), - 604 => Ok(Self::InvalidSelfProgram), - _ => Err(code), - } - } -} - /// Program-specific error codes for the subscriptions program. /// /// Error codes are grouped by category: diff --git a/program/src/event_engine.rs b/program/src/event_engine.rs index f2825a8..527ed7b 100644 --- a/program/src/event_engine.rs +++ b/program/src/event_engine.rs @@ -56,14 +56,6 @@ pub trait EventDiscriminator { const DISCRIMINATOR: u8; } -#[cfg(test)] -fn discriminator_bytes() -> Vec { - let mut bytes = Vec::with_capacity(EVENT_DISCRIMINATOR_LEN); - bytes.extend_from_slice(&EVENT_IX_TAG_LE); - bytes.push(T::DISCRIMINATOR); - bytes -} - /// Serializes an event into its wire format: tag + discriminator + field data. pub trait EventSerialize: EventDiscriminator { /// The length of the serialized event data (excluding discriminator). @@ -169,94 +161,28 @@ pub fn emit_event( mod tests { use super::*; - struct StubEventA { + struct StubEvent { value: u64, } - impl EventDiscriminator for StubEventA { + impl EventDiscriminator for StubEvent { const DISCRIMINATOR: u8 = 10; } - impl EventSerialize for StubEventA { + impl EventSerialize for StubEvent { const DATA_LEN: usize = 8; fn write_inner(&self, writer: &mut Vec) { writer.extend_from_slice(&self.value.to_le_bytes()); } } - struct StubEventB { - flag: u8, - } - - impl EventDiscriminator for StubEventB { - const DISCRIMINATOR: u8 = 20; - } - - impl EventSerialize for StubEventB { - const DATA_LEN: usize = 1; - fn write_inner(&self, writer: &mut Vec) { - writer.push(self.flag); - } - } - - #[test] - fn constants_are_consistent() { - assert_eq!(EVENT_IX_TAG_LE, EVENT_IX_TAG.to_le_bytes()); - assert_eq!(EVENT_DISCRIMINATOR_LEN, 8 + 1); - } - - #[test] - fn discriminator_bytes_has_correct_prefix() { - let disc = discriminator_bytes::(); - assert_eq!(disc.len(), EVENT_DISCRIMINATOR_LEN); - assert_eq!(&disc[..8], &EVENT_IX_TAG_LE); - assert_eq!(disc[8], StubEventA::DISCRIMINATOR); - } - - #[test] - fn discriminator_bytes_differ_per_event() { - let a = discriminator_bytes::(); - let b = discriminator_bytes::(); - assert_ne!(a, b); - assert_eq!(&a[..8], &b[..8]); - assert_ne!(a[8], b[8]); - } - #[test] fn to_bytes_prepends_tag_and_discriminator() { - let event = StubEventA { value: 42 }; + let event = StubEvent { value: 42 }; let bytes = event.to_bytes(); assert_eq!(&bytes[..8], &EVENT_IX_TAG_LE); - assert_eq!(bytes[8], StubEventA::DISCRIMINATOR); + assert_eq!(bytes[8], StubEvent::DISCRIMINATOR); assert_eq!(&bytes[9..], &42u64.to_le_bytes()); } - - #[test] - fn to_bytes_equals_discriminator_bytes_plus_inner() { - let event = StubEventA { value: 999 }; - let full = event.to_bytes(); - - let mut inner = Vec::new(); - event.write_inner(&mut inner); - - let mut expected = discriminator_bytes::(); - expected.extend_from_slice(&inner); - assert_eq!(full, expected); - } - - #[test] - fn write_inner_is_only_field_data() { - let event = StubEventB { flag: 0xFF }; - let mut inner = Vec::new(); - event.write_inner(&mut inner); - assert_eq!(inner, vec![0xFF]); - } - - #[test] - fn different_events_produce_different_wire_bytes() { - let a = StubEventA { value: 1 }; - let b = StubEventB { flag: 1 }; - assert_ne!(a.to_bytes(), b.to_bytes()); - } } diff --git a/program/src/instructions/emit_event.rs b/program/src/instructions/emit_event.rs index 54b3576..cd54f00 100644 --- a/program/src/instructions/emit_event.rs +++ b/program/src/instructions/emit_event.rs @@ -1,4 +1,4 @@ -use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; +use pinocchio::{error::ProgramError, AccountView, ProgramResult}; use crate::event_engine::verify_event_authority; @@ -7,7 +7,7 @@ use crate::event_engine::verify_event_authority; /// This instruction only verifies that the caller is the event authority PDA. /// It exists so that indexers can detect event data in the inner instruction /// log. It is never invoked directly by external callers. -pub fn process(_program_id: &Address, accounts: &[AccountView]) -> ProgramResult { +pub fn process(accounts: &[AccountView]) -> ProgramResult { let [event_authority] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/program/src/instructions/helpers/delegation.rs b/program/src/instructions/helpers/delegation.rs index d80b095..6dc76f5 100644 --- a/program/src/instructions/helpers/delegation.rs +++ b/program/src/instructions/helpers/delegation.rs @@ -92,7 +92,7 @@ pub fn create_delegation_account( Seed::from(&bump_bytes), ]; - ProgramAccount::init::<()>(accounts.payer, accounts.delegation_account, &seeds, space)?; + ProgramAccount::init(accounts.payer, accounts.delegation_account, &seeds, space)?; Ok((bump, init_id, mint)) } diff --git a/program/src/instructions/helpers/plan.rs b/program/src/instructions/helpers/plan.rs index 5a30407..407e281 100644 --- a/program/src/instructions/helpers/plan.rs +++ b/program/src/instructions/helpers/plan.rs @@ -62,7 +62,7 @@ pub fn create_plan_account(accounts: &CreatePlanAccounts, plan_id: u64) -> Resul Seed::from(&bump_bytes[..]), ]; - ProgramAccount::init::<()>(accounts.payer, accounts.plan_pda, &seeds, Plan::LEN)?; + ProgramAccount::init(accounts.payer, accounts.plan_pda, &seeds, Plan::LEN)?; Ok(bump) } diff --git a/program/src/instructions/helpers/program.rs b/program/src/instructions/helpers/program.rs index 1e5e1b2..5105acc 100644 --- a/program/src/instructions/helpers/program.rs +++ b/program/src/instructions/helpers/program.rs @@ -24,12 +24,7 @@ impl AccountCheck for ProgramAccount { /// Creates a PDA account idempotently, handling the case where an attacker /// has pre-funded the PDA address with lamports to block creation. impl ProgramAccountInit for ProgramAccount { - fn init<'a, T: Sized>( - payer: &AccountView, - account: &AccountView, - seeds: &[Seed<'a>], - space: usize, - ) -> ProgramResult { + fn init(payer: &AccountView, account: &AccountView, seeds: &[Seed], space: usize) -> ProgramResult { let lamports = Rent::get()?.try_minimum_balance(space)?; let signer = [Signer::from(seeds)]; diff --git a/program/src/instructions/helpers/token.rs b/program/src/instructions/helpers/token.rs index 5083fb4..2253d52 100644 --- a/program/src/instructions/helpers/token.rs +++ b/program/src/instructions/helpers/token.rs @@ -1,24 +1,17 @@ -//! Token account validation, initialization, and interface helpers. +//! Token account validation and interface helpers. //! -//! Provides [`AccountCheck`] and init implementations for both SPL Token and +//! Provides [`AccountCheck`] implementations for both SPL Token and //! Token-2022 mints and token accounts, along with unified interface types //! ([`MintInterface`], [`TokenAccountInterface`], [`TokenProgramInterface`]) //! that dispatch to the correct variant based on account ownership. -use pinocchio::{ - error::ProgramError, - sysvars::{rent::Rent, Sysvar}, - AccountView, Address, ProgramResult, -}; -use pinocchio_associated_token_account::instructions::Create; -use pinocchio_system::instructions::CreateAccount; +use pinocchio::{error::ProgramError, AccountView, Address}; use pinocchio_token::{ - instructions::{InitializeAccount3, InitializeMint2}, state::{Account as TokenAccountState, Mint}, ID as SPL_TOKEN_PROGRAM_ID, }; -use super::traits::{AccountCheck, AssociatedTokenAccountCheck, AssociatedTokenAccountInit, MintInit, TokenInit}; +use super::traits::{AccountCheck, AssociatedTokenAccountCheck}; use crate::{ constants::{ MINT_IS_INITIALIZED_OFFSET, TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET, TOKEN_2022_MINT_DISCRIMINATOR, @@ -42,38 +35,6 @@ pub fn get_token_account_delegate(data: &[u8]) -> Result, Subscr Ok(Some(Address::from(delegate))) } -// Private helpers to consolidate initialization logic - -fn init_mint_helper( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - owner_program_id: &Address, -) -> ProgramResult { - let lamports = Rent::get()?.try_minimum_balance(Mint::LEN)?; - - CreateAccount { from: payer, to: account, lamports, space: Mint::LEN as u64, owner: owner_program_id }.invoke()?; - - InitializeMint2 { mint: account, decimals, mint_authority, freeze_authority }.invoke() -} - -fn init_token_helper( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &Address, - owner_program_id: &Address, -) -> ProgramResult { - let lamports = Rent::get()?.try_minimum_balance(TokenAccountState::LEN)?; - - CreateAccount { from: payer, to: account, lamports, space: TokenAccountState::LEN as u64, owner: owner_program_id } - .invoke()?; - - InitializeAccount3 { account, mint, owner }.invoke() -} - // MintAccount (SPL Token) /// Validation for SPL Token mint accounts. @@ -98,31 +59,6 @@ impl AccountCheck for MintAccount { } } -impl MintInit for MintAccount { - fn init( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult { - init_mint_helper(account, payer, decimals, mint_authority, freeze_authority, &pinocchio_token::ID) - } - - fn init_if_needed( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult { - match Self::check(account) { - Ok(_) => Ok(()), - Err(_) => Self::init(account, payer, decimals, mint_authority, freeze_authority), - } - } -} - // TokenAccount (SPL Token) /// Validation for SPL Token token accounts. @@ -142,24 +78,6 @@ impl AccountCheck for TokenAccount { } } -impl TokenInit for TokenAccount { - fn init(account: &AccountView, mint: &AccountView, payer: &AccountView, owner: &Address) -> ProgramResult { - init_token_helper(account, mint, payer, owner, &pinocchio_token::ID) - } - - fn init_if_needed( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &Address, - ) -> ProgramResult { - match Self::check(account) { - Ok(_) => Ok(()), - Err(_) => Self::init(account, mint, payer, owner), - } - } -} - // Mint2022Account /// Validation for Token-2022 mint accounts. @@ -191,38 +109,6 @@ impl AccountCheck for Mint2022Account { } } -impl MintInit for Mint2022Account { - fn init( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult { - init_mint_helper( - account, - payer, - decimals, - mint_authority, - freeze_authority, - &crate::constants::TOKEN_2022_PROGRAM_ID, - ) - } - - fn init_if_needed( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult { - match Self::check(account) { - Ok(_) => Ok(()), - Err(_) => Self::init(account, payer, decimals, mint_authority, freeze_authority), - } - } -} - // TokenAccount2022Account /// Validation for Token-2022 token accounts. @@ -250,24 +136,6 @@ impl AccountCheck for TokenAccount2022Account { } } -impl TokenInit for TokenAccount2022Account { - fn init(account: &AccountView, mint: &AccountView, payer: &AccountView, owner: &Address) -> ProgramResult { - init_token_helper(account, mint, payer, owner, &crate::constants::TOKEN_2022_PROGRAM_ID) - } - - fn init_if_needed( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &Address, - ) -> ProgramResult { - match Self::check(account) { - Ok(_) => Ok(()), - Err(_) => Self::init(account, mint, payer, owner), - } - } -} - /// Unified validator that accepts either SPL Token or Token-2022 program accounts. pub struct TokenProgramInterface; @@ -280,7 +148,7 @@ impl TokenProgramInterface { } } -/// Unified validator/initializer for mint accounts across both SPL Token and Token-2022. +/// Unified validator for mint accounts across both SPL Token and Token-2022. pub struct MintInterface; impl AccountCheck for MintInterface { @@ -305,7 +173,7 @@ impl MintInterface { } } -/// Unified validator/initializer for token accounts across both SPL Token and Token-2022. +/// Unified validator for token accounts across both SPL Token and Token-2022. pub struct TokenAccountInterface; impl AccountCheck for TokenAccountInterface { @@ -334,45 +202,15 @@ impl TokenAccountInterface { accounts: &[&AccountView], ) -> Result<(), ProgramError> { for account in accounts { - Self::check(account)?; - - if !account.owned_by(token_program.address()) { - return Err(SubscriptionsError::InvalidTokenProgram.into()); - } + Self::check_with_program(account, token_program)?; } Ok(()) } } -/// Unified ATA check and creation for both SPL Token and Token-2022. +/// Unified ATA check for both SPL Token and Token-2022. pub struct AssociatedTokenAccount; -impl AssociatedTokenAccount { - /// Verifies that the given account is a valid ATA using the provided bump. - /// This is cheaper than the trait method as it doesn't derive the bump. - pub fn check_with_bump( - account: &AccountView, - authority: &AccountView, - mint: &AccountView, - token_program: &AccountView, - bump: u8, - ) -> Result<(), ProgramError> { - TokenAccountInterface::check(account)?; - - let expected_pda = Address::create_program_address( - &[authority.address().as_ref(), token_program.address().as_ref(), mint.address().as_ref(), &[bump]], - &pinocchio_associated_token_account::ID, - ) - .map_err(|_| SubscriptionsError::InvalidAssociatedTokenAccountDerivedAddress)?; - - if expected_pda.ne(account.address()) { - return Err(SubscriptionsError::InvalidAssociatedTokenAccountDerivedAddress.into()); - } - - Ok(()) - } -} - impl AssociatedTokenAccountCheck for AssociatedTokenAccount { fn check( account: &AccountView, @@ -395,30 +233,3 @@ impl AssociatedTokenAccountCheck for AssociatedTokenAccount { Ok(()) } } - -impl AssociatedTokenAccountInit for AssociatedTokenAccount { - fn init( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &AccountView, - system_program: &AccountView, - token_program: &AccountView, - ) -> ProgramResult { - Create { funding_account: payer, account, wallet: owner, mint, system_program, token_program }.invoke() - } - - fn init_if_needed( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &AccountView, - system_program: &AccountView, - token_program: &AccountView, - ) -> ProgramResult { - match Self::check(account, owner, mint, token_program) { - Ok(_) => Ok(()), - Err(_) => Self::init(account, mint, payer, owner, system_program, token_program), - } - } -} diff --git a/program/src/instructions/helpers/traits.rs b/program/src/instructions/helpers/traits.rs index 1c75d14..b4d709e 100644 --- a/program/src/instructions/helpers/traits.rs +++ b/program/src/instructions/helpers/traits.rs @@ -1,6 +1,6 @@ //! Traits for account validation, initialization, and lifecycle operations. -use pinocchio::{cpi::Seed, error::ProgramError, AccountView, Address, ProgramResult}; +use pinocchio::{cpi::Seed, error::ProgramError, AccountView, ProgramResult}; /// Performs a read-only validation check on an account (e.g., ownership, size, discriminator). pub trait AccountCheck { @@ -9,37 +9,6 @@ pub trait AccountCheck { fn check(account: &AccountView) -> Result<(), ProgramError>; } -/// Initializes an SPL Token mint account. -pub trait MintInit { - /// Creates and initializes a new mint. - fn init( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult; - - /// Initializes the mint only if it does not already exist. - fn init_if_needed( - account: &AccountView, - payer: &AccountView, - decimals: u8, - mint_authority: &Address, - freeze_authority: Option<&Address>, - ) -> ProgramResult; -} - -/// Initializes an SPL Token account. -pub trait TokenInit { - /// Creates and initializes a new token account. - fn init(account: &AccountView, mint: &AccountView, payer: &AccountView, owner: &Address) -> ProgramResult; - - /// Initializes the token account only if it does not already exist. - fn init_if_needed(account: &AccountView, mint: &AccountView, payer: &AccountView, owner: &Address) - -> ProgramResult; -} - /// Validates that an account is the correct Associated Token Account for the given inputs. pub trait AssociatedTokenAccountCheck { /// Checks ATA derivation against authority, mint, and token program. @@ -51,38 +20,10 @@ pub trait AssociatedTokenAccountCheck { ) -> Result<(), ProgramError>; } -/// Creates an Associated Token Account via CPI. -pub trait AssociatedTokenAccountInit { - /// Creates a new ATA. - fn init( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &AccountView, - system_program: &AccountView, - token_program: &AccountView, - ) -> ProgramResult; - - /// Creates the ATA only if it does not already exist. - fn init_if_needed( - account: &AccountView, - mint: &AccountView, - payer: &AccountView, - owner: &AccountView, - system_program: &AccountView, - token_program: &AccountView, - ) -> ProgramResult; -} - /// Creates a program-owned PDA account via CPI. pub trait ProgramAccountInit { /// Allocates `space` bytes, assigns to this program, and funds rent from `payer`. - fn init<'a, T: Sized>( - payer: &AccountView, - account: &AccountView, - seeds: &[Seed<'a>], - space: usize, - ) -> ProgramResult; + fn init(payer: &AccountView, account: &AccountView, seeds: &[Seed], space: usize) -> ProgramResult; } /// Closes a program-owned account, returning lamports to `destination`. diff --git a/program/src/instructions/helpers/transfer_context.rs b/program/src/instructions/helpers/transfer_context.rs index afac374..c772830 100644 --- a/program/src/instructions/helpers/transfer_context.rs +++ b/program/src/instructions/helpers/transfer_context.rs @@ -55,7 +55,7 @@ pub fn open<'a>( let bump_bytes = [bump]; let seeds = [Seed::from(TransferContext::SEED), Seed::from(subscription_authority.as_ref()), Seed::from(&bump_bytes)]; - ProgramAccount::init::(input.initiator, context, &seeds, TransferContext::LEN)?; + ProgramAccount::init(input.initiator, context, &seeds, TransferContext::LEN)?; let mut writable = *context; let mut data = writable.try_borrow_mut()?; diff --git a/program/src/instructions/initialize_subscription_authority.rs b/program/src/instructions/initialize_subscription_authority.rs index 0151c07..34dc7b5 100644 --- a/program/src/instructions/initialize_subscription_authority.rs +++ b/program/src/instructions/initialize_subscription_authority.rs @@ -85,12 +85,7 @@ pub fn process(accounts: &mut [AccountView]) -> ProgramResult { // stored payer. The original sponsor recorded at first creation remains // the rent recipient on close. if accounts.subscription_authority.data_len() == 0 { - ProgramAccount::init::( - accounts.payer, - accounts.subscription_authority, - &seeds, - SubscriptionAuthority::LEN, - )?; + ProgramAccount::init(accounts.payer, accounts.subscription_authority, &seeds, SubscriptionAuthority::LEN)?; let init_id = Clock::get()?.slot as i64; let mut data = accounts.subscription_authority.try_borrow_mut()?; diff --git a/program/src/instructions/subscribe.rs b/program/src/instructions/subscribe.rs index 858e059..76010d3 100644 --- a/program/src/instructions/subscribe.rs +++ b/program/src/instructions/subscribe.rs @@ -142,12 +142,7 @@ pub fn process(accounts: &mut [AccountView], data: &SubscribeData) -> ProgramRes Seed::from(&bump_bytes[..]), ]; - ProgramAccount::init::<()>( - accounts_struct.payer, - accounts_struct.subscription_pda, - &seeds, - SubscriptionDelegation::LEN, - )?; + ProgramAccount::init(accounts_struct.payer, accounts_struct.subscription_pda, &seeds, SubscriptionDelegation::LEN)?; // Initialize subscription state { diff --git a/program/src/state/common.rs b/program/src/state/common.rs index 46076ef..1a7632a 100644 --- a/program/src/state/common.rs +++ b/program/src/state/common.rs @@ -88,19 +88,6 @@ impl TryFrom for AccountDiscriminator { } } -impl From for u8 { - fn from(val: AccountDiscriminator) -> Self { - match val { - AccountDiscriminator::SubscriptionAuthority => 0, - AccountDiscriminator::Plan => 1, - AccountDiscriminator::FixedDelegation => 2, - AccountDiscriminator::RecurringDelegation => 3, - AccountDiscriminator::SubscriptionDelegation => 4, - AccountDiscriminator::TransferContext => 5, - } - } -} - /// Lifecycle status of a subscription [`Plan`](super::plan::Plan). #[repr(u8)] #[derive(Clone, Copy, PartialEq, Debug, CodamaType)] diff --git a/program/src/state/header.rs b/program/src/state/header.rs index ad9c577..7f3ee1d 100644 --- a/program/src/state/header.rs +++ b/program/src/state/header.rs @@ -85,7 +85,7 @@ impl Header { init_id: i64, ) { self.version = CURRENT_VERSION; - self.discriminator = discriminator.into(); + self.discriminator = discriminator as u8; self.bump = bump; self.delegator = *delegator; self.delegatee = *delegatee; @@ -94,9 +94,11 @@ impl Header { } } -pub const HEADER_LEN_V1: usize = 107; -const _: () = assert!(Header::LEN == HEADER_LEN_V1); -const _: () = assert!(INIT_ID_OFFSET == 99); -const _: () = assert!(DELEGATOR_OFFSET == 3); -const _: () = assert!(DELEGATEE_OFFSET == 35); -const _: () = assert!(PAYER_OFFSET == 67); +const _: () = assert!(Header::LEN == 107); +const _: () = assert!(core::mem::offset_of!(Header, discriminator) == DISCRIMINATOR_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, version) == VERSION_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, bump) == BUMP_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, delegator) == DELEGATOR_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, delegatee) == DELEGATEE_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, payer) == PAYER_OFFSET); +const _: () = assert!(core::mem::offset_of!(Header, init_id) == INIT_ID_OFFSET); diff --git a/program/src/state/versioning/core.rs b/program/src/state/versioning/core.rs index 9d60c5f..37acada 100644 --- a/program/src/state/versioning/core.rs +++ b/program/src/state/versioning/core.rs @@ -162,11 +162,6 @@ mod tests { assert_custom_error(err, SubscriptionsError::InvalidAccountData); } - #[test] - fn test_min_size_both_zero_ok() { - assert!(check_min_account_size(0, 0).is_ok()); - } - #[test] fn test_min_size_exact_ok() { assert!(check_min_account_size(100, 100).is_ok()); @@ -182,10 +177,4 @@ mod tests { let err = check_min_account_size(80, 100).unwrap_err(); assert_custom_error(err, SubscriptionsError::InvalidAccountData); } - - #[test] - fn test_min_size_zero_len_err() { - let err = check_min_account_size(0, 100).unwrap_err(); - assert_custom_error(err, SubscriptionsError::InvalidAccountData); - } } diff --git a/webapp/src/lib/api-client.ts b/webapp/src/lib/api-client.ts index 5718e48..2ad4bd5 100644 --- a/webapp/src/lib/api-client.ts +++ b/webapp/src/lib/api-client.ts @@ -92,13 +92,6 @@ export interface FullConfig { networks: Record; } -export function clusterIdToNetwork(id: string): string { - if (id.includes('devnet')) return 'devnet'; - if (id.includes('testnet')) return 'testnet'; - if (id.includes('mainnet')) return 'mainnet'; - return 'localnet'; -} - export const api = { config: { getAll: () => apiClient('/api/config'),