Skip to content

feat: add magic-action-shared-payer and magic-actions-on-delegation examples#79

Open
crocodiledundalk wants to merge 2 commits intomagicblock-labs:mainfrom
crocodiledundalk:additional-magic-action-examples
Open

feat: add magic-action-shared-payer and magic-actions-on-delegation examples#79
crocodiledundalk wants to merge 2 commits intomagicblock-labs:mainfrom
crocodiledundalk:additional-magic-action-examples

Conversation

@crocodiledundalk
Copy link
Copy Markdown

@crocodiledundalk crocodiledundalk commented May 4, 2026

Summary

Two new Magic Actions examples that extend the existing magic-actions demo.

magic-action-shared-payer

Shows how to use a protocol-owned PDA (global_signer, seeds: [b"global_signer"]) as the escrow authority for Magic Action callbacks instead of the individual user's wallet. Uses MagicInstructionBuilder::build() + manual AccountMeta patching + invoke_signed to authorize the PDA over the MagicProgram CPI.

magic-actions-on-delegation

Demonstrates post-delegation actions: an increment instruction is bundled into the delegate call via delegate_account_with_actions + PostDelegationActions. The ER validator fires it automatically when the account is first cloned — no separate client transaction needed.

Also adds:

  • docs/post-delegation-actions.md — specification for the PostDelegationActions key-table encoding (compact AccountMeta, encryption variants, signer constraints)
  • Root README.md updated to list both new examples and link to the new guide

Test plan

  • cd magic-action-shared-payer && yarn && anchor test passes
  • cd magic-actions-on-delegation && yarn && anchor test passes
  • Both example READMEs render correctly on GitHub
  • Root README links resolve correctly

Summary by CodeRabbit

  • Documentation

    • Added comprehensive guide for post-delegation actions (lifecycle, encryption options, validator/merge rules, diagnostics).
    • Added example links for "Magic Action - Shared Payer" and "Magic Actions on Delegation".
  • New Features

    • Added two example projects demonstrating magic-action patterns with on-chain handlers and end-to-end test suites.
  • Chores

    • Added project configs, tooling scripts, and ignore rules to support building and testing the examples.

…xamples

magic-action-shared-payer
  Extends the magic-actions example to use a protocol-owned global_signer
  PDA (seeds: [b"global_signer"]) as the escrow authority instead of the
  user's wallet. Uses MagicInstructionBuilder::build() + manual AccountMeta
  patch + invoke_signed so the PDA can sign the MagicProgram CPI without
  being initialized.

magic-actions-on-delegation
  Demonstrates post-delegation actions: an increment instruction is queued
  at delegation time using delegate_account_with_actions + PostDelegationActions,
  and the ER validator fires it automatically when the account is first
  cloned — no separate client transaction required.

Also adds:
  - docs/post-delegation-actions.md: specification for PostDelegationActions
    encoding (key table, compact AccountMeta, encryption variants)
  - README.md: entries for both new examples and a Guides section
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

@crocodiledundalk is attempting to deploy a commit to the MagicBlock Labs Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Walkthrough

Adds a detailed "Post-Delegation Actions" doc and two Anchor example projects—magic-actions-on-delegation and magic-action-shared-payer—each with Anchor/Rust programs, configs, TypeScript tests, and README/docs. The root README is updated to link both examples.

Changes

Post-Delegation Actions Documentation and Example

Layer / File(s) Summary
Conceptual Documentation
docs/post-delegation-actions.md
Adds end-to-end guide for post-delegation actions: lifecycle, client/CPI builder patterns, encryption constraints, account/signer validation, funding/runtime constraints, and a failure checklist.
Project Configuration
magic-actions-on-delegation/Cargo.toml, magic-actions-on-delegation/Anchor.toml, magic-actions-on-delegation/package.json, magic-actions-on-delegation/.gitignore, magic-actions-on-delegation/.prettierignore, magic-actions-on-delegation/tsconfig.json
New workspace and tooling config pinning Anchor v0.32.1 and SDK deps; release profile and TypeScript settings; lint/test scripts.
Program Implementation
magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs
Adds Anchor program magic_actions_on_delegation with Counter PDA, initialize, increment, delegate (queues encrypted post-delegation Increment action), and undelegate (commit/undelegate CPI).
Wiring and Deployment
magic-actions-on-delegation/migrations/deploy.ts, magic-actions-on-delegation/programs/.../Cargo.toml
Adds Anchor migration stub and crate manifest for the new program.
Tests
magic-actions-on-delegation/tests/magic-actions-on-delegation.ts
End-to-end tests exercising initialize, delegation with queued post-action execution on ER, cross-layer reads, and undelegate flows.
Docs / README
magic-actions-on-delegation/README.md
Project README with pinned tool versions and build/test instructions.

Magic Actions Shared Payer Example

Layer / File(s) Summary
Project Configuration
magic-action-shared-payer/Cargo.toml, magic-action-shared-payer/Anchor.toml, magic-action-shared-payer/package.json, magic-action-shared-payer/.gitignore, magic-action-shared-payer/.prettierignore, magic-action-shared-payer/tsconfig.json
New workspace and tooling config pinning Anchor v0.32.1 and SDK deps; release profile and TypeScript settings; lint/test scripts.
Program Implementation
magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs
Adds Anchor program magic_action_shared_payer with Counter and Leaderboard PDAs, initialize, increment, update_leaderboard, delegate, undelegate, and commit_and_update_leaderboard which builds a Magic action using a shared global_signer and performs CPI invoke_signed.
Wiring and Deployment
magic-action-shared-payer/migrations/deploy.ts, magic-action-shared-payer/programs/.../Cargo.toml
Adds Anchor migration stub and crate manifest for the new program.
Tests
magic-action-shared-payer/tests/magic-action-shared-payer.ts
Integration tests covering base/ER flows, escrow top-up, delegation, ER increment, commit with leaderboard update via Magic action, transaction inspection helpers, and sqlite commit-signature queries.
Docs / README
magic-action-shared-payer/README.md, README.md
Project README with pinned tool versions and build/test instructions; root README.md updated to link both examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • GabrielePicco
  • jonasXchen
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main changes: adding two new Magic Actions example applications (magic-action-shared-payer and magic-actions-on-delegation) to the repository.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown
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: 8

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

Inline comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 38-42: In update_leaderboard, don't deserialize
ctx.accounts.counter blindly; first validate the account is the expected PDA and
owned by the allowed program(s) before reading its bytes. Derive the expected
PDA (using the same seeds used to create the Counter account), compare it to
ctx.accounts.counter.to_account_info().key, and assert the account owner equals
the expected program id (or system program if appropriate); also check the
account data length/is_initialized flag if present. Only after these checks
succeed, call try_borrow_data and Counter::try_deserialize to mutate
high_score/leaderboard.

In `@magic-action-shared-payer/README.md`:
- Around line 41-42: The README currently instructs users to run "rm -rf
/target/deploy/*.keypair" which uses an absolute path and can delete /target on
a machine; change the instruction to use a repository-relative path instead
(e.g., "target/deploy/*.keypair" or "./target/deploy/*.keypair") in the README
so it targets the repo's target/deploy directory; update the line referencing
the command (the rm -rf /target/deploy/*.keypair entry) to the safer relative
form and consider adding a short safety note or existence check before removal.

In `@magic-action-shared-payer/tests/magic-action-shared-payer.ts`:
- Around line 292-302: Replace the fixed animated sleep helper
(sleepWithAnimation) and any hard-coded sleeps used before
delegate/commit/undelegate assertions with a polling helper that repeatedly
checks the expected on-chain/state condition until it becomes true or a
configurable timeout elapses; implement a function like
waitForCondition(checkFn, timeoutMs, intervalMs) and use it in the tests where
delegate/commit/undelegate state is asserted (replace calls to
sleepWithAnimation and direct setTimeout waits) so each test polls the relevant
getter/contract call (e.g., the delegate status, commit inclusion, or undelegate
balance) at short intervals and fails only after the timeout instead of waiting
a fixed duration.

In `@magic-action-shared-payer/tsconfig.json`:
- Around line 3-8: The tsconfig.json restricts global type resolution to
["mocha","chai"], causing Node types used by migrations/deploy.ts (which uses
module.exports) to be missing; update the "types" array in tsconfig.json to
include "node" (e.g., ["mocha","chai","node"]) so `@types/node` is picked up and
the migration entrypoint type-checks correctly.

In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`:
- Around line 163-164: The delegation_program account (field delegation_program)
is unconstrained and can be swapped by callers; add an address constraint to
ensure it is the expected MagicBlock delegation program before performing the
PDA-signed CPI. Update the struct field annotation for delegation_program to
include an address check that compares to the MagicBlock delegation program's ID
constant (for example using #[account(address =
<magic_block_program_crate>::ID)] or the appropriate crate/program ID constant),
and keep references in the CPI call sites such as delegate_account_with_actions
unchanged so the inner invocation only ever receives the verified program.

In `@magic-actions-on-delegation/README.md`:
- Around line 38-42: Update the README command that currently uses an absolute
path "/target/deploy/*.keypair" to a project-local path; replace the command
string (rm -rf /target/deploy/*.keypair) with a safe relative version such as rm
-rf target/deploy/*.keypair or rm -f ./target/deploy/*.keypair so it deletes the
local target/deploy keypairs instead of touching the filesystem root.

In `@magic-actions-on-delegation/tests/magic-actions-on-delegation.ts`:
- Around line 193-195: Replace fixed sleep() waits with a polling helper that
repeatedly calls readCounterOnER() or readCounterOnBase() until the expected
value is observed or a real timeout elapses; implement a function like
waitForCounterOnER(expected: number, timeoutMs: number, intervalMs = 500) and
similarly waitForCounterOnBase(...) that measures elapsed time, loops awaiting
the corresponding readCounter... call every interval, returns when value
matches, and throws an error on timeout, then replace all uses of sleep(seconds)
in the tests with calls to the appropriate waitForCounter... helper to avoid
flaky fixed delays.

In `@magic-actions-on-delegation/tsconfig.json`:
- Around line 3-8: The tsconfig currently restricts ambient types to
["mocha","chai"] but migrations/deploy.ts uses CommonJS module.exports, so add
Node ambient types: update the "types" array in
magic-actions-on-delegation/tsconfig.json to include "node" (e.g.
["mocha","chai","node"]) and also add `@types/node` to magic-actions-on-delegation
package.json devDependencies so TypeScript recognizes Node globals and module
typings used by module.exports in migrations/deploy.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 001d36c7-81f6-4dd0-9c0f-a3e1f2dd0bb1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d91320 and 78f2d1d.

⛔ Files ignored due to path filters (4)
  • magic-action-shared-payer/Cargo.lock is excluded by !**/*.lock
  • magic-action-shared-payer/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • magic-actions-on-delegation/Cargo.lock is excluded by !**/*.lock
  • magic-actions-on-delegation/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (24)
  • README.md
  • docs/post-delegation-actions.md
  • magic-action-shared-payer/.gitignore
  • magic-action-shared-payer/.prettierignore
  • magic-action-shared-payer/Anchor.toml
  • magic-action-shared-payer/Cargo.toml
  • magic-action-shared-payer/README.md
  • magic-action-shared-payer/migrations/deploy.ts
  • magic-action-shared-payer/package.json
  • magic-action-shared-payer/programs/magic-action-shared-payer/Cargo.toml
  • magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs
  • magic-action-shared-payer/tests/magic-action-shared-payer.ts
  • magic-action-shared-payer/tsconfig.json
  • magic-actions-on-delegation/.gitignore
  • magic-actions-on-delegation/.prettierignore
  • magic-actions-on-delegation/Anchor.toml
  • magic-actions-on-delegation/Cargo.toml
  • magic-actions-on-delegation/README.md
  • magic-actions-on-delegation/migrations/deploy.ts
  • magic-actions-on-delegation/package.json
  • magic-actions-on-delegation/programs/magic-actions-on-delegation/Cargo.toml
  • magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs
  • magic-actions-on-delegation/tests/magic-actions-on-delegation.ts
  • magic-actions-on-delegation/tsconfig.json

Comment thread magic-action-shared-payer/README.md Outdated
Comment on lines +292 to +302
async function sleepWithAnimation(seconds: number): Promise<void> {
const totalMs = seconds * 1000;
const interval = 500;
const iterations = Math.floor(totalMs / interval);
for (let i = 0; i < iterations; i++) {
const dots = ".".repeat((i % 3) + 1);
process.stdout.write(`\rWaiting${dots} `);
await new Promise(resolve => setTimeout(resolve, interval));
}
process.stdout.write("\r\x1b[K");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use state polling instead of animated fixed waits.

The delegate/commit/undelegate assertions are gated by hard-coded sleeps, so this suite will intermittently fail whenever cross-layer propagation is slower than expected. Poll the relevant state transitions with a timeout rather than sleeping for 5–10 seconds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magic-action-shared-payer/tests/magic-action-shared-payer.ts` around lines
292 - 302, Replace the fixed animated sleep helper (sleepWithAnimation) and any
hard-coded sleeps used before delegate/commit/undelegate assertions with a
polling helper that repeatedly checks the expected on-chain/state condition
until it becomes true or a configurable timeout elapses; implement a function
like waitForCondition(checkFn, timeoutMs, intervalMs) and use it in the tests
where delegate/commit/undelegate state is asserted (replace calls to
sleepWithAnimation and direct setTimeout waits) so each test polls the relevant
getter/contract call (e.g., the delegate status, commit inclusion, or undelegate
balance) at short intervals and fails only after the timeout instead of waiting
a fixed duration.

Comment thread magic-action-shared-payer/tsconfig.json Outdated
Comment thread magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs Outdated
Comment thread magic-actions-on-delegation/README.md Outdated
Comment on lines +193 to +195
function sleep(seconds: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace fixed sleeps with polling-based waits.

These tests assume clone/commit propagation always finishes within 8–10 seconds. That will flake under slower validators or CI load; poll readCounterOnER() / readCounterOnBase() until the expected value or a real timeout instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magic-actions-on-delegation/tests/magic-actions-on-delegation.ts` around
lines 193 - 195, Replace fixed sleep() waits with a polling helper that
repeatedly calls readCounterOnER() or readCounterOnBase() until the expected
value is observed or a real timeout elapses; implement a function like
waitForCounterOnER(expected: number, timeoutMs: number, intervalMs = 500) and
similarly waitForCounterOnBase(...) that measures elapsed time, loops awaiting
the corresponding readCounter... call every interval, returns when value
matches, and throws an error on timeout, then replace all uses of sleep(seconds)
in the tests with calls to the appropriate waitForCounter... helper to avoid
flaky fixed delays.

Comment thread magic-actions-on-delegation/tsconfig.json Outdated
- README: fix absolute path /target/deploy -> target/deploy (both examples)
- UpdateLeaderboard.counter: add seeds constraint to lock PDA address
  regardless of owner (varies between delegated/undelegated state)
- DelegateCounter.delegation_program: add address constraint using
  ephemeral_rollups_sdk::id() to prevent caller substitution
- tsconfig.json: add "node" to types array so migrations/deploy.ts
  module.exports typechecks correctly (both examples)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
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

♻️ Duplicate comments (1)
magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs (1)

38-42: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Still validate the counter owner before deserializing its bytes.

The new seeds constraint fixes the PDA address, but update_leaderboard still trusts raw bytes from an UncheckedAccount without checking that the owner is one of the expected programs for the delegated/undelegated states. A wrong owner or malformed account at that PDA can still make this action read invalid state or fail unexpectedly.

Also applies to: 145-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`
around lines 38 - 42, The code in update_leaderboard calls
Counter::try_deserialize on an UncheckedAccount (counter) without verifying the
account owner; before deserializing, assert that counter.to_account_info().owner
matches one of the allowed program IDs for the delegated/undelegated counter
states (e.g., compare to the expected program pubkeys/constants) and return an
appropriate error if not, then proceed to call Counter::try_deserialize; apply
the same owner check before any other Counter::try_deserialize usage (e.g., the
other occurrence around lines 145-146) so you never deserialize bytes from an
account with an unexpected owner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 31-35: The increment function uses unchecked addition on
counter.count which will wrap at u64::MAX; change the logic in pub fn
increment(ctx: Context<Increment>) -> Result<()> to use
counter.count.checked_add(1) and if it returns None return an explicit error
(e.g., a custom OverflowError or ProgramError::Custom) instead of performing the
addition; update any associated error enum (or define a new variant) so callers
can see the overflow failure and keep the msg! log only after a successful
checked add.
- Around line 22-26: The initialize function currently mutates shared PDA
accounts (counter and leaderboard) unguarded, allowing any caller to rerun and
zero them; change the account constraints used where you see init_if_needed for
these PDAs to init so they can only be created once, or alternatively add an
explicit authority/initialized guard in the initialize function that checks a
designated authority signer or an "initialized" flag before writing (and return
an error if already initialized). Apply the same change to the other
initialization block in this file that sets count/high_score to 0 (the other
function/section using the same counter/leaderboard pattern).

In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`:
- Around line 26-27: The increment function (pub fn increment in lib.rs)
currently does unchecked u64 addition; change it to use checked_add(1) and
return a proper error if None is returned to prevent overflow. Add the suggested
ErrorCode enum with a CounterOverflow variant (#[error_code] pub enum ErrorCode
{ #[msg("counter overflow")] CounterOverflow, }) and update increment to set
ctx.accounts.counter.count =
ctx.accounts.counter.count.checked_add(1).ok_or(ErrorCode::CounterOverflow)?; so
the handler returns an error instead of silently wrapping on overflow.
- Around line 20-21: The initialize handler currently allows reinitialization
and wiping of the global PDA counter by using init_if_needed and then
unconditionally setting ctx.accounts.counter.count = 0; update the account
constraint so the PDA is created only once by replacing init_if_needed with init
on the Counter account (the PDA derived from COUNTER_SEED) and remove or guard
the unconditional reset in the initialize function (Context<Initialize> /
initialize) so the counter is not overwritten on subsequent calls; ensure the
account is created with the same seeds and bump used elsewhere and that access
control prevents repeat creation or modification by unauthorized signers.

---

Duplicate comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 38-42: The code in update_leaderboard calls
Counter::try_deserialize on an UncheckedAccount (counter) without verifying the
account owner; before deserializing, assert that counter.to_account_info().owner
matches one of the allowed program IDs for the delegated/undelegated counter
states (e.g., compare to the expected program pubkeys/constants) and return an
appropriate error if not, then proceed to call Counter::try_deserialize; apply
the same owner check before any other Counter::try_deserialize usage (e.g., the
other occurrence around lines 145-146) so you never deserialize bytes from an
account with an unexpected owner.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b20ec78-f526-4f80-b656-9fe03413bb68

📥 Commits

Reviewing files that changed from the base of the PR and between 78f2d1d and 23156e8.

📒 Files selected for processing (6)
  • magic-action-shared-payer/README.md
  • magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs
  • magic-action-shared-payer/tsconfig.json
  • magic-actions-on-delegation/README.md
  • magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs
  • magic-actions-on-delegation/tsconfig.json

Comment on lines +22 to +26
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
let leaderboard = &mut ctx.accounts.leaderboard;
leaderboard.high_score = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

initialize is still a public reset for the shared PDAs.

Because Lines 23-26 always write zeroes, these init_if_needed constraints let any caller rerun initialize after setup and wipe both the counter and leaderboard. If this state is meant to be one-time initialization, make the accounts init instead, or add an explicit authority/initialized guard.

Suggested fix
-    #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [COUNTER_SEED], bump)]
+    #[account(init, payer = user, space = 8 + 8, seeds = [COUNTER_SEED], bump)]
     pub counter: Account<'info, Counter>,
-    #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [LEADERBOARD_SEED], bump)]
+    #[account(init, payer = user, space = 8 + 8, seeds = [LEADERBOARD_SEED], bump)]
     pub leaderboard: Account<'info, Leaderboard>,

Also applies to: 124-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`
around lines 22 - 26, The initialize function currently mutates shared PDA
accounts (counter and leaderboard) unguarded, allowing any caller to rerun and
zero them; change the account constraints used where you see init_if_needed for
these PDAs to init so they can only be created once, or alternatively add an
explicit authority/initialized guard in the initialize function that checks a
designated authority signer or an "initialized" flag before writing (and return
an error if already initialized). Apply the same change to the other
initialization block in this file that sets count/high_score to 0 (the other
function/section using the same counter/leaderboard pattern).

Comment on lines +31 to +35
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("PDA {} count: {}", counter.key(), counter.count);
Ok(())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use checked arithmetic for count.

Line 33 can wrap on-chain once count reaches u64::MAX, which silently resets the counter and can corrupt the observed high score. Please switch this to checked_add and return an explicit error on overflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`
around lines 31 - 35, The increment function uses unchecked addition on
counter.count which will wrap at u64::MAX; change the logic in pub fn
increment(ctx: Context<Increment>) -> Result<()> to use
counter.count.checked_add(1) and if it returns None return an explicit error
(e.g., a custom OverflowError or ProgramError::Custom) instead of performing the
addition; update any associated error enum (or define a new variant) so callers
can see the overflow failure and keep the msg! log only after a successful
checked add.

Comment on lines +20 to +21
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && wc -l lib.rs

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 97


🏁 Script executed:

cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | head -40

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 1825


🏁 Script executed:

cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | sed -n '100,130p'

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 1084


🏁 Script executed:

cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && grep -n "init_if_needed\|init," lib.rs

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 114


🏁 Script executed:

cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | sed -n '1,100p' | grep -A 15 "pub struct Initialize"

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 68


initialize can reset the shared PDA counter after deployment.

init_if_needed allows the handler to execute on an existing account. Since Line 21 unconditionally writes 0 to the counter, any signer can re-call initialize and wipe the counter state. This PDA is global (derived from COUNTER_SEED only), and there is no access control preventing repeated calls. Change init_if_needed to init to prevent reinitialization.

Suggested fix
 #[derive(Accounts)]
 pub struct Initialize<'info> {
     #[account(
-        init_if_needed,
+        init,
         payer = user,
         space = 8 + 8,
         seeds = [COUNTER_SEED],
         bump,
     )]
     pub counter: Account<'info, Counter>,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`
around lines 20 - 21, The initialize handler currently allows reinitialization
and wiping of the global PDA counter by using init_if_needed and then
unconditionally setting ctx.accounts.counter.count = 0; update the account
constraint so the PDA is created only once by replacing init_if_needed with init
on the Counter account (the PDA derived from COUNTER_SEED) and remove or guard
the unconditional reset in the initialize function (Context<Initialize> /
initialize) so the counter is not overwritten on subsequent calls; ensure the
account is created with the same seeds and bump used elsewhere and that access
control prevents repeat creation or modification by unauthorized signers.

Comment on lines +26 to +27
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count += 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "lib.rs" | head -20

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 1193


🏁 Script executed:

cat -n magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs

Repository: magicblock-labs/magicblock-engine-examples

Length of output: 7810


Guard the counter increment against overflow.

Line 27 uses unchecked u64 arithmetic. In BPF (release) builds, overflow wraps silently, corrupting state. Use checked_add(1) with proper error handling.

Suggested fix
 pub fn increment(ctx: Context<Increment>) -> Result<()> {
-    ctx.accounts.counter.count += 1;
+    ctx.accounts.counter.count = ctx
+        .accounts
+        .counter
+        .count
+        .checked_add(1)
+        .ok_or(ErrorCode::CounterOverflow)?;
     msg!(
         "PDA {} count: {} (signer: {})",
         ctx.accounts.counter.key(),
         ctx.accounts.counter.count,
         ctx.accounts.user.key(),

Add an error variant alongside the instruction code:

#[error_code]
pub enum ErrorCode {
    #[msg("counter overflow")]
    CounterOverflow,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`
around lines 26 - 27, The increment function (pub fn increment in lib.rs)
currently does unchecked u64 addition; change it to use checked_add(1) and
return a proper error if None is returned to prevent overflow. Add the suggested
ErrorCode enum with a CounterOverflow variant (#[error_code] pub enum ErrorCode
{ #[msg("counter overflow")] CounterOverflow, }) and update increment to set
ctx.accounts.counter.count =
ctx.accounts.counter.count.checked_add(1).ok_or(ErrorCode::CounterOverflow)?; so
the handler returns an error instead of silently wrapping on overflow.

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