Skip to content

Add macros for V2#14

Open
dhruvja wants to merge 3 commits intomagicblock-labs:masterfrom
dhruvja:dhruvja/v2-macros
Open

Add macros for V2#14
dhruvja wants to merge 3 commits intomagicblock-labs:masterfrom
dhruvja:dhruvja/v2-macros

Conversation

@dhruvja
Copy link

@dhruvja dhruvja commented Nov 29, 2025

Problem

#9 introduced V2 instructions but didnt have macros that could be used in the contract.

Solution

Add macros to support V2 instructions similar to V1.

Summary by CodeRabbit

  • New Features

    • Added support for V2 session tokens, enabling dual-variant session token handling alongside existing V1 tokens.
  • Bug Fixes

    • Corrected session token expiration logic to properly detect expired tokens.
  • Chores

    • Updated anchor-lang dependency to version 0.31.0.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

Walkthrough

This PR updates the anchor-lang dependency to version 0.31.0, refactors the Session derive macro to support both V1 and V2 session token variants through conditional logic, adds a backward compatibility alias, and reverses the expiration check logic in session token validation functions.

Changes

Cohort / File(s) Change Summary
Dependency Version Update
programs/gpl_session/Cargo.toml
Updated anchor-lang from ^0 to "0.31.0" in dependencies
Macro Variant Support
programs/gpl_session/macros/attribute/src/lib.rs
Added SessionTokenType enum (V1/V2) and get_session_token_type helper for parsing token variants. Renamed primary derive macro from Session to SessionV2, with branching logic to generate appropriate Session or SessionV2 impl based on detected token type. Added derive_v2 function as a backward compatibility alias that delegates to derive. Updated documentation to reflect support for both SessionToken and SessionTokenV2
Expiration Logic
programs/gpl_session/src/lib.rs
Reversed expiration check logic in is_expired functions for both SessionToken and SessionTokenV2 to evaluate tokens as expired when current time is after valid_until (instead of before)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Macro variant detection logic: Verify SessionTokenType enum and get_session_token_type helper correctly identify V1 and V2 token variants from type annotations
  • Derive macro branching: Ensure both Session (V1) and SessionV2 (V2) implementation paths generate correct trait implementations with appropriate field mappings
  • Backward compatibility alias: Confirm derive_v2 function properly delegates to derive and preserves existing Session derive behavior
  • Expiration semantics: Validate the reversed comparison logic is semantically correct and aligns with intended token expiration behavior across both token variants

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective of adding V2 macro support, directly reflecting the primary change in the changeset.
Description check ✅ Passed The description identifies the problem, references a related PR, and explains the solution, but lacks deployment details and follows a minimal template structure compared to the repository's detailed template.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
programs/gpl_session/macros/attribute/src/lib.rs (1)

82-104: Clarify macro naming and documentation.

The macro is now named SessionV2 (line 83), but the comment says it "supports both SessionToken and SessionTokenV2" (line 82). This is confusing because:

  1. The macro name suggests it's only for V2
  2. The derive actually generates either Session or SessionV2 trait depending on the field type

Consider updating the comment to clarify that the macro name is SessionV2 but it automatically detects and supports both token types.

Apply this diff:

-// Macro to derive Session Trait (supports both SessionToken and SessionTokenV2)
+// Macro to derive Session traits - automatically detects the token type and generates
+// either Session (for SessionToken) or SessionV2 (for SessionTokenV2) trait implementation.
 #[proc_macro_derive(SessionV2, attributes(session))]
 pub fn derive(input: TokenStream) -> TokenStream {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41318cb and a6e53bf.

📒 Files selected for processing (3)
  • programs/gpl_session/Cargo.toml (1 hunks)
  • programs/gpl_session/macros/attribute/src/lib.rs (4 hunks)
  • programs/gpl_session/src/lib.rs (1 hunks)
🔇 Additional comments (4)
programs/gpl_session/macros/attribute/src/lib.rs (3)

30-80: LGTM - Token type detection is thorough.

The SessionTokenType enum and get_session_token_type() function correctly identify whether a field is of type Option<Account<'info, SessionToken>> (V1) or Option<Account<'info, SessionTokenV2>> (V2). The nested type checking is comprehensive.


123-176: LGTM - Trait implementations are correctly generated.

The match statement correctly generates:

  • Session trait implementation with SessionToken type for V1
  • SessionV2 trait implementation with SessionTokenV2 type for V2

Both implementations provide the required methods: target_program(), session_token(), session_authority(), and session_signer().


198-201: LGTM - Documentation updated for dual-variant support.

The comment correctly indicates that the session_auth_or macro works with both Session and SessionV2 traits, which is accurate given that it calls generic methods like session_token(), is_valid(), and session_authority() that are implemented by both traits.

programs/gpl_session/Cargo.toml (1)

23-23: anchor-lang version 0.31.0 is valid and free from known vulnerabilities.

Verification confirms that version 0.31.0 exists, was released on March 8–9, 2025, and is available on crates.io. No publicly disclosed CVEs or high-severity security advisories were found for this version. Notably, v0.31.0 includes significant security improvements addressing stack-memory overflow risks and dependency compatibility issues with solana-program. The pinned version is appropriate for use.

Comment on lines +181 to +185
// Macro to derive SessionV2 Trait (alias for Session macro for backwards compatibility)
#[proc_macro_derive(Session, attributes(session))]
pub fn derive_v2(input: TokenStream) -> TokenStream {
derive(input)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Clarify backward compatibility comment.

The comment on line 181 states "alias for Session macro for backwards compatibility", but this is misleading. The derive_v2 function makes Session an alias that calls the same derive function as SessionV2, rather than the other way around.

Both #[derive(Session)] and #[derive(SessionV2)] now invoke the same implementation that auto-detects the token type. This means existing code using #[derive(Session)] with SessionToken fields will continue to work.

Consider clarifying the comment.

Apply this diff:

-// Macro to derive SessionV2 Trait (alias for Session macro for backwards compatibility)
+// Backward compatibility: #[derive(Session)] now delegates to the same implementation
+// as #[derive(SessionV2)], auto-detecting whether to generate Session or SessionV2 trait.
 #[proc_macro_derive(Session, attributes(session))]
 pub fn derive_v2(input: TokenStream) -> TokenStream {
     derive(input)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Macro to derive SessionV2 Trait (alias for Session macro for backwards compatibility)
#[proc_macro_derive(Session, attributes(session))]
pub fn derive_v2(input: TokenStream) -> TokenStream {
derive(input)
}
// Backward compatibility: #[derive(Session)] now delegates to the same implementation
// as #[derive(SessionV2)], auto-detecting whether to generate Session or SessionV2 trait.
#[proc_macro_derive(Session, attributes(session))]
pub fn derive_v2(input: TokenStream) -> TokenStream {
derive(input)
}
🤖 Prompt for AI Agents
In programs/gpl_session/macros/attribute/src/lib.rs around lines 181 to 185, the
comment for derive_v2 is misleading; update it to clarify that derive_v2
implements the same derive function as SessionV2 and that both
#[derive(Session)] and #[derive(SessionV2)] now delegate to the same
implementation which auto-detects token types so existing uses of
#[derive(Session)] continue to work; replace the current wording with a concise
note stating that Session is an alias that forwards to the shared derive
implementation (same as SessionV2) for backward compatibility and token
auto-detection.

Comment on lines 457 to 460
pub fn is_expired(&self) -> Result<bool> {
let now = Clock::get()?.unix_timestamp;
Ok(now < self.valid_until)
Ok(now > self.valid_until)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Inverted expiration logic breaks V2 validation.

The is_expired() method returns true when now > self.valid_until (when expired), but validate() at line 481 directly returns is_expired(), and is_valid() calls validate(). This means is_valid() will return true when the token is expired, which is incorrect.

At line 218, the code requires is_valid() to be true: require!(ctx.accounts.is_valid()?, SessionError::InvalidToken). With the current logic, this will accept expired tokens and reject valid ones.

Note that V1's is_expired() (line 425) has the opposite behavior: it returns true when now < valid_until (when NOT expired). While the V1 naming is incorrect, the behavior is correct because validate() returns this value.

Apply this diff to fix the logic:

 impl SessionTokenV2 {
     pub fn is_expired(&self) -> Result<bool> {
         let now = Clock::get()?.unix_timestamp;
-        Ok(now > self.valid_until)
+        Ok(now < self.valid_until)
     }

Or, for better clarity, rename the method to is_valid() and update all call sites:

 impl SessionTokenV2 {
-    pub fn is_expired(&self) -> Result<bool> {
+    pub fn is_valid(&self) -> Result<bool> {
         let now = Clock::get()?.unix_timestamp;
-        Ok(now > self.valid_until)
+        Ok(now < self.valid_until)
     }

     // validate the token
     pub fn validate(&self, ctx: ValidityCheckerV2) -> Result<bool> {
         let target_program = ctx.target_program;
         let session_signer = ctx.session_signer.key();
         let authority = ctx.authority.key();

         // Check the PDA seeds
         let seeds = &[
             SessionTokenV2::SEED_PREFIX.as_bytes(),
             target_program.as_ref(),
             session_signer.as_ref(),
             authority.as_ref(),
         ];

         let (pda, _) = Pubkey::find_program_address(seeds, &crate::id());

         require_eq!(pda, ctx.session_token.key(), SessionError::InvalidToken);

         // Check if the token has expired
-        self.is_expired()
+        self.is_valid()
     }
 }

And update line 378:

 pub fn revoke_session_token_handler_v2(ctx: Context<RevokeSessionTokenV2>) -> Result<()> {
     // If the session is still active, the authority must be a signer
-    if !ctx.accounts.session_token.is_expired()?  {
+    if ctx.accounts.session_token.is_valid()?  {
         require!(ctx.accounts.authority.is_signer, SessionError::InvalidAuthority);
     }
     Ok(())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn is_expired(&self) -> Result<bool> {
let now = Clock::get()?.unix_timestamp;
Ok(now < self.valid_until)
Ok(now > self.valid_until)
}
pub fn is_expired(&self) -> Result<bool> {
let now = Clock::get()?.unix_timestamp;
Ok(now < self.valid_until)
}
🤖 Prompt for AI Agents
programs/gpl_session/src/lib.rs around lines 457-460: the current is_expired()
returns true when now > valid_until (i.e. when expired) but callers expect a
truthy value for valid tokens, causing expired tokens to be accepted; change the
method so it returns false when expired and true when still valid (e.g., return
Ok(now < self.valid_until)) or rename the method to is_valid() and invert its
logic, then update all call sites accordingly (including the call at line 378
and the validate()/is_valid() wrappers) so they check the corrected boolean
semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant