Skip to content

feat: call handler v2#143

Merged
taco-paco merged 8 commits intomainfrom
feat/call-handler-v2
Feb 18, 2026
Merged

feat: call handler v2#143
taco-paco merged 8 commits intomainfrom
feat/call-handler-v2

Conversation

@taco-paco
Copy link
Contributor

@taco-paco taco-paco commented Feb 12, 2026

⚠️ NOTE: Use notes like this to emphasize something important about the PR.

This could include other PRs this PR is built on top of; API breaking changes; reasons for why the PR is on hold; or anything else you would like to draw attention to.

Status Type ⚠️ Core Change Issue
Ready Feature No Link

Problem

What problem are you trying to solve?
The users must be able to get source program from where the action was scheduled

Solution

How did you solve the problem?

Before & After Screenshots

Insert screenshots of example code output

BEFORE:
[insert screenshot here]

AFTER:
[insert screenshot here]

Other changes (e.g. bug fixes, small refactors)

Deploy Notes

Notes regarding deployment of the contained body of work. These should note any
new dependencies, new scripts, etc.

New scripts:

  • script : script details

New dependencies:

  • dependency : dependency details

Summary by CodeRabbit

  • New Features
    • Added CallHandlerV2 flow with instruction builder and size-budget helper; new escrow validation error messages.
  • Deprecation
    • Legacy call handler marked deprecated (since 1.1.4); migrate to CallHandlerV2.
  • Refactor
    • Streamlined call-handler logic to reuse centralized error messages and simplified account handling.
  • Tests
    • Added extensive unit and integration tests covering finalize/undelegate v2 flows, transfers, and invalid-escrow error cases.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Walkthrough

Adds a new CallHandlerV2 instruction and processing path, a deprecated legacy builder, a new instruction builder and size-budget helper, escrow-related error constants, processor/account changes for CPI-style handling, module re-exports, and extensive Rust/JS tests exercising finalize and undelegate flows.

Changes

Cohort / File(s) Summary
Discriminator & routing
src/discriminator.rs, src/lib.rs
Added DlpDiscriminator::CallHandlerV2 = 20 and wired the new discriminator into slow-path dispatch.
Instruction builders
src/instruction_builder/call_handler.rs, src/instruction_builder/call_handler_v2.rs, src/instruction_builder/mod.rs
Marked legacy call_handler deprecated; added call_handler_v2 builder and call_handler_v2_size_budget; added module and public re-export.
Processor logic
src/processor/call_handler_v2.rs, src/processor/mod.rs, src/processor/call_handler.rs
Added process_call_handler_v2 implementing CPI-style invocation with escrow PDA validation and signer seeds; moved escrow error messages to crate::error; adjusted call_handler to import those constants and simplified account checks.
Errors
src/error.rs
Added public error message constants INVALID_ESCROW_PDA and INVALID_ESCROW_OWNER.
Tests & test program
tests/test_call_handler_v2.rs, tests/integration/programs/test-delegation/src/lib.rs, tests/integration/tests/test-delegation.ts
Added comprehensive ProgramTest unit tests, new v2 handlers and account contexts in test delegation program, and new JS integration tests and helper instruction builders/PDAs for call_handler_v2 flows.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • snawaz
  • GabrielePicco
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description follows the template structure but lacks substantive content. The 'Solution' section is empty, deployment notes contain only placeholders, and implementation details are absent. Complete the 'Solution' section with details on how call handler v2 works, fill in actual deployment notes if applicable, and remove placeholder text from screenshots and dependencies.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: call handler v2' clearly and concisely describes the main change: adding a new call handler v2 feature throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/call-handler-v2

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
Contributor

@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: 4

🤖 Fix all issues with AI agents
In `@src/discriminator.rs`:
- Around line 46-47: The doc comment for the enum variant CallHandlerV2 in
discriminator.rs is missing a trailing period; update the comment text "See
[crate::processor::process_call_handler_v2] for docs" to end with a period so it
matches the style of other variant comments (i.e., "See
[crate::processor::process_call_handler_v2] for docs.").

In `@src/processor/call_handler_v2.rs`:
- Around line 80-87: The log message in the executable check for source_program
prints destination_program.key by mistake; update the msg! call inside the
non-executable branch (the block that returns
Err(ProgramError::InvalidAccountData)) to reference source_program.key instead
of destination_program.key so the error correctly identifies the source program.
- Around line 19-31: The doc comment misnumbers accounts (duplicate index 3) and
makes the PDA origin ambiguous; update the account index list so indices are
sequential and match the named accounts: 0 = `[signer]` validator, 1 = `[]`
validator fee vault, 2 = `[]` destination program, 3 = `[]` source program, 4 =
`[]` escrow_authority, 5 = `[writable]` non-delegated escrow PDA created from
escrow_authority (index 4), 6 = `[readonly/writable]` other accounts needed for
action, 7 = `[readonly/writable]` other accounts needed for action, etc.; also
replace the ambiguous phrase “escrow pda created from 3” with “escrow PDA
created from escrow_authority (index 4)” to keep the documentation consistent
with function semantics in call_handler_v2.

In `@tests/test_call_handler_v2.rs`:
- Around line 186-202: The delegation metadata is being created with the wrong
seed index (using ephemeral_balance_seeds_from_payer!(payer.pubkey(), 0)) while
the actual ephemeral balance PDA (ephemeral_balance_pda) was derived with index
1; update the seeds passed to create_delegation_metadata_data and the
program_test.add_account call so they use
ephemeral_balance_seeds_from_payer!(payer.pubkey(), 1) (or otherwise match the
index used to derive ephemeral_balance_pda) so
delegation_metadata_pda_from_delegated_account(&ephemeral_balance_pda) and
create_delegation_metadata_data(...) use the same seed index.

Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@tests/integration/programs/test-delegation/src/lib.rs`:
- Around line 120-161: Both v2 handlers duplicate v1 logic; extract the shared
transfer logic into a private helper (e.g.,
perform_escrow_transfer(ctx_accounts: &T, amount: u64) or similar) and call it
from commit_base_action_handler_v2 and undelegate_base_action_handler_v2 to
remove verbatim duplication while preserving the extra counter update in
undelegate_base_action_handler_v2; ensure the helper accepts the context types
or account infos (used in Transfer creation) and that undelegate still
increments the Counter after calling the helper.

In `@tests/integration/tests/test-delegation.ts`:
- Around line 660-696: createCallHandlerV2Instruction relies on Buffer.alloc
zero-fill for bytes 1–7 of the 8-byte discriminator (only byte 0 is set), so
explicitly write the full 8-byte discriminator to the data buffer (e.g., use a
64-bit write like writeBigUInt64LE(BigInt(20), 0) or explicitly zero bytes 1–7
and set byte 0) before writing escrowIndex and lengths; also review the top-up
helper (the function that takes amount at line ~633) and consider using BigInt
for its amount parameter or clearly document/guard against
Number.MAX_SAFE_INTEGER to avoid silent precision loss.
- Around line 630-658: The discriminator for TopUpEphemeralBalance in
createTopUpEphemeralBalanceInstruction only writes the first byte and relies on
Buffer.alloc zero-fill; change it to the same explicit Buffer.from([...])
pattern used elsewhere by encoding all 8 discriminator bytes explicitly (instead
of data.writeUint8(9,0)), then append the u64 amount and u8 index bytes in order
so the final data buffer matches other instruction builders (keep references to
ephemeralBalancePdaFromPayer and DELEGATION_PROGRAM_ID intact).

Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@src/processor/call_handler_v2.rs`:
- Around line 66-73: Add the same explicit executability check for
destination_program as is done for source_program: verify
destination_program.executable, emit a clear msg! referencing
destination_program.key (e.g., "{} program is not executable: destination
program"), and return Err(ProgramError::InvalidAccountData) on failure so
callers get a specific early error instead of a generic CPI failure; locate this
change near the existing source_program.executable check in call_handler_v2.rs.

Copy link
Contributor

@GabrielePicco GabrielePicco left a comment

Choose a reason for hiding this comment

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

LGTM!
Left a comment to address before merging

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/processor/call_handler_v2.rs`:
- Around line 79-95: The current construction of accounts_meta and
handler_accounts can duplicate entries when other_accounts contains keys equal
to source_program, escrow_authority_account, or escrow_account; update the
iterator used to build (accounts_meta, handler_accounts) to exclude any account
whose key equals validator.key OR equals source_program.key OR equals
escrow_authority_account.key OR equals escrow_account.key (or alternatively
deduplicate by key before appending the three fixed accounts) so the final three
entries are guaranteed to be the appended fixed accounts; adjust the map that
creates AccountMeta and the collected handler_accounts accordingly to avoid
duplicates.
- Around line 67-77: The code never verifies that the plain account passed as
source_program is executable, so add an explicit executability check for
source_program (e.g., right after the PDA checks for escrow_account) and return
a clear error (use or add INVALID_SOURCE_PROGRAM) if source_program.executable
is false; implement this by testing source_program.executable (or call the
project helper like require_executable if available) and returning the
appropriate ProgramError/early Err from the function before performing the CPI
so an arbitrary data account cannot be supplied as the source.

Comment on lines +67 to +77
// verify passed escrow_account derived from escrow authority
let escrow_seeds: &[&[u8]] =
ephemeral_balance_seeds_from_payer!(escrow_authority_account.key, args.escrow_index);
let escrow_bump = load_pda(
escrow_account,
escrow_seeds,
&crate::id(),
true,
INVALID_ESCROW_PDA,
)?;
load_owned_pda(escrow_account, &system_program::id(), INVALID_ESCROW_OWNER)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

source_program executability is never verified.

source_program is passed as a plain account in the CPI (not the program_id), so the Solana runtime will not enforce that it is executable. Without an explicit check, any arbitrary data account can be supplied as source_program, giving the destination program a misleading "source" key — which directly undermines the feature's stated goal ("users must be able to get source program from where the action was scheduled").

A prior code review on this PR observed that source_program was checked for executability ("source_program is checked for executability, correctly, since it's passed as a plain account to CPI and the runtime won't validate it"), implying the check may have been removed in a later iteration.

🛡️ Proposed fix
     // verify passed escrow_account derived from escrow authority
+    if !source_program.executable {
+        return Err(ProgramError::InvalidAccountData);
+    }
     let escrow_seeds: &[&[u8]] =
         ephemeral_balance_seeds_from_payer!(escrow_authority_account.key, args.escrow_index);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/processor/call_handler_v2.rs` around lines 67 - 77, The code never
verifies that the plain account passed as source_program is executable, so add
an explicit executability check for source_program (e.g., right after the PDA
checks for escrow_account) and return a clear error (use or add
INVALID_SOURCE_PROGRAM) if source_program.executable is false; implement this by
testing source_program.executable (or call the project helper like
require_executable if available) and returning the appropriate
ProgramError/early Err from the function before performing the CPI so an
arbitrary data account cannot be supplied as the source.

@taco-paco taco-paco merged commit 30fb2b6 into main Feb 18, 2026
4 checks passed
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.

2 participants

Comments