Skip to content

feat: implement ephemeral accounts#915

Open
bmuddha wants to merge 14 commits intomasterfrom
bmuddha/feat/ephemeral-account
Open

feat: implement ephemeral accounts#915
bmuddha wants to merge 14 commits intomasterfrom
bmuddha/feat/ephemeral-account

Conversation

@bmuddha
Copy link
Collaborator

@bmuddha bmuddha commented Jan 28, 2026

Summary

Implements ephemeral accounts — zero-balance accounts that live only in the ephemeral rollup, with rent paid by a sponsor at 32 lamports/byte (~109x cheaper than Solana).

This enables cheap temporary storage for gaming sessions, caching, computation intermediate results, etc. — without base chain prerequisites.

What's included:

  • Three new instructions: CreateEphemeralAccount, ResizeEphemeralAccount, CloseEphemeralAccount
  • CPI-only architecture (programs mediate access)
  • Rent flows through a vault account, fully refundable on close
  • Comprehensive test suite (~1,100 lines)

See MIMD-0016 for full spec (rent analysis, safeguards, example usage).

Compatibility

  • No breaking changes
  • Config change (describe):
  • Migration needed (describe):

Testing

  • tests added (ephemeral_accounts.rs with coverage for create/resize/close flows)

Checklist

Summary by CodeRabbit

  • New Features

    • Ephemeral accounts: create, resize, and close low-cost temporary accounts and an ephemeral vault funded at startup; sponsor-aware invocation supported.
  • Validation & Safety

    • New runtime checks to prevent improper use, mutation, or committing of ephemeral accounts; ownership and rent rules enforced.
  • Tests

    • Extensive integration test suite covering lifecycle, edge cases, failure paths, and sponsor scenarios.
  • Chores

    • Updated Solana dependency revisions.

@github-actions
Copy link

github-actions bot commented Jan 28, 2026

Manual Deploy Available

You can trigger a manual deploy of this PR branch to testnet:

Deploy to Testnet 🚀

Alternative: Comment /deploy on this PR to trigger deployment directly.

⚠️ Note: Manual deploy requires authorization. Only authorized users can trigger deployments.

Comment updated automatically when the PR is synchronized.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end ephemeral-account support: new MagicBlock instruction variants (CreateEphemeralAccount, ResizeEphemeralAccount, CloseEphemeralAccount) and processor wiring; implements ephemeral account modules (validation, create, resize, close) with rent calculation and transfer logic; sponsor-aware CPI helpers and global sponsor PDA support; EPHEMERAL_VAULT funding and blacklisting; ScheduleCommit guard to reject ephemeral accounts; extensive integration tests covering lifecycle and failure cases; dependency rev bumps and a small lock-type rename (GlobalWriteLock → GlobalSyncLock).

Assessment against linked issues

Objective Addressed Explanation
Create/Resize/Close instructions implemented in magic-program [#914]
Sponsor validation, vault initialization, and vault blacklisting [#914]
Ephemeral accounts rejected in ScheduleCommit and comprehensive tests added [#914]
EPHEMERAL flag added to AccountSharedData [#914] No direct change to the AccountSharedData definition shown in diffs; only ephemeral() checks and marking usage are present.

Out-of-scope changes

Code Change Explanation
Rename GlobalWriteLock → GlobalSyncLock (magicblock-accounts-db/src/lib.rs; magicblock-processor/src/executor/mod.rs) Rename/refactor of lock type unrelated to ephemeral-account implementation objectives.
Public API surface expansion and new re-exports (programs/magicblock/src/lib.rs) Broad addition of modules and public re-exports increases crate surface beyond the ephemeral-account feature scope.

Suggested reviewers

  • thlorenz
  • GabrielePicco
  • Dodecahedr0x
✨ 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 bmuddha/feat/ephemeral-account

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

🤖 Fix all issues with AI agents
In `@magicblock-api/src/fund_account.rs`:
- Around line 86-101: fund_ephemeral_vault currently relies on unwrap() after
get_account and leaves the vault owner as Default::default(); change it so the
created vault is explicitly owned by the magic program and remove the unwrap:
after calling fund_account(...) fetch the account with get_account and handle
the Option without unwrap (e.g., if let Some(mut vault) =
accountsdb.get_account(&magic_program::EPHEMERAL_VAULT_PUBKEY) {
vault.set_owner(magic_program::ID); vault.set_delegated(true);
vault.set_ephemeral(true); let _ =
accountsdb.insert_account(&magic_program::EPHEMERAL_VAULT_PUBKEY, &vault); }
else { /* return or log an error per existing error-handling conventions */ }),
ensuring you reference fund_ephemeral_vault, fund_account,
magic_program::EPHEMERAL_VAULT_PUBKEY and magic_program::ID.

In `@magicblock-magic-program-api/src/instruction.rs`:
- Around line 119-127: Update the ResizeEphemeralAccount account reference docs
to include the vault account that process_resize.rs expects at index 2;
specifically, add a third bullet like "**2.** `[WRITE]` Vault account
(holds/receives lamports for rent transfer)" so the documentation matches the
implementation that transfers rent to/from the vault when resizing in
process_resize.rs and references the ResizeEphemeralAccount variant.
- Around line 129-134: Update the documentation for the CloseEphemeralAccount
enum variant to include the missing vault account at index 2: annotate that
**2.** is a `[WRITE]` Vault account (source of rent refund). Locate the
CloseEphemeralAccount variant (symbol: CloseEphemeralAccount) and modify its doc
comment so the account reference list includes the new third line describing the
vault account, matching the implementation in process_close.rs that debits the
vault to refund rent.

In `@magicblock-processor/tests/ephemeral_accounts.rs`:
- Around line 902-958: The test function test_create_with_pda_sponsor is
documented as IGNORED but currently runs; either add the #[ignore] attribute
above fn test_create_with_pda_sponsor to actually skip it, or update the block
comment to remove the "NOTE: This test is IGNORED" text so the comment matches
current behavior; locate the test by the function name
test_create_with_pda_sponsor in ephemeral_accounts.rs and apply one of these two
fixes consistently.

In `@programs/magicblock/src/ephemeral_accounts/process_close.rs`:
- Around line 65-68: The close logic currently zeroes lamports, sets owner and
resizes the account but doesn't clear the ephemeral flag; call
acc.set_ephemeral(false) on the same mutable account handle (the same variable
used with ephemeral.borrow_mut()/acc) as part of the close sequence (e.g.,
alongside set_lamports, set_owner, resize) so the ephemeral flag is cleared and
the pubkey can be reused.

In `@programs/magicblock/src/ephemeral_accounts/process_create.rs`:
- Around line 35-40: Replace the owner-only vault check with an explicit pubkey
equality check: use get_instruction_pubkey_with_idx(transaction_context, 2)? to
fetch the account pubkey and compare it against EPHEMERAL_VAULT_PUBKEY,
returning InstructionError::InvalidAccountOwner (or existing error) if they
differ. Update this check in process_create
(ephemeral_accounts/process_create.rs), and apply the same replacement in
process_resize (ephemeral_accounts/process_resize.rs) and process_close
(ephemeral_accounts/process_close.rs), ensuring you remove or keep but do not
rely solely on the previous *vault.borrow().owner() check and instead validate
the exact vault pubkey.
- Around line 23-25: The code calls validate_cpi_only(transaction_context)? but
later uses an unbounded data_len for account resize and rent_for; add a
module-level constant (e.g., MAX_DATA_LEN) and check data_len <= MAX_DATA_LEN at
the start of process_create (or the function handling resizing) and return a
proper error if exceeded. Also use checked arithmetic (checked_add/checked_mul)
when computing total account size or rent to avoid overflow before calling
resize/rent_for; reference the data_len argument, the resize call, and rent_for
call so the guard is applied immediately before those operations.

In `@programs/magicblock/src/ephemeral_accounts/process_resize.rs`:
- Around line 15-77: The resize logic in process_resize_ephemeral_account
performs lamport transfers even when delta == 0; update the function to
short-circuit that work by checking delta == 0 after computing delta
(rent_for(new_data_len) - rent_for(old_len)) and, if zero, skip both
debit/credit calls and proceed directly to ephemeral.borrow_mut().resize(...)
and logging; reference the delta variable and the debit/credit calls
(accounts::debit_instruction_account_at_index and
accounts::credit_instruction_account_at_index) so the zero-case branch is the
only change.

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

🤖 Fix all issues with AI agents
In `@magicblock-magic-program-api/src/instruction.rs`:
- Around line 119-134: Update the account reference docs for both
ResizeEphemeralAccount and CloseEphemeralAccount to include the vault account
(account index 2) that the processors expect; specifically state a third
account: **2.** `[WRITE]` Vault account (holds ephemeral assets/rent) so
clients/SDKs supply three accounts when invoking the ResizeEphemeralAccount {
new_data_len: usize } and CloseEphemeralAccount instructions (this matches the
processors that read account index 2).
- Around line 114-127: Change the variable-width usize fields used in the
CreateEphemeralAccount and ResizeEphemeralAccount enum variants to fixed-width
u64 so instruction payloads are architecture independent: replace
CreateEphemeralAccount::data_len: usize and
ResizeEphemeralAccount::new_data_len: usize with u64, update any
serialization/deserialization, constructor calls, and tests that reference these
fields (e.g., where CreateEphemeralAccount or ResizeEphemeralAccount instances
are created or pattern-matched) to use u64 values.

In `@magicblock-processor/tests/ephemeral_accounts.rs`:
- Around line 902-958: The comment says the test is ignored but it actually
runs; update the test declaration for fn test_create_with_pda_sponsor to match
the comment by adding the #[ignore] attribute above the async test (or
alternatively update the comment to remove the "IGNORED" wording). Locate the
async test function test_create_with_pda_sponsor in tests/ephemeral_accounts.rs
and either prepend #[ignore] to the #[tokio::test] or change the note text so it
no longer claims the test is ignored.

In `@programs/guinea/src/lib.rs`:
- Around line 205-319: The three handlers create_ephemeral_account,
resize_ephemeral_account, and close_ephemeral_account duplicate the same
validation of magic_program_info and vault_info; extract that logic into a small
helper (e.g., validate_ephemeral_accounts) that accepts &AccountInfo for
magic_program_info and vault_info and returns ProgramResult, checking
magic_program_info.key against magicblock_magic_program_api::ID and
vault_info.key against EPHEMERAL_VAULT_PUBKEY; then call
validate_ephemeral_accounts(magic_program_info, vault_info)? at the start of
each handler and remove the duplicated checks.

In `@programs/magicblock/src/ephemeral_accounts/process_close.rs`:
- Around line 69-72: The close path leaves the account's ephemeral marker set so
subsequent CreateEphemeralAccount calls will reject it; update the close logic
in process_close.rs (the ephemeral / acc variable) to explicitly clear the
ephemeral flag after zeroing the lamports/size (i.e., after acc.set_lamports(0)
and acc.resize(0, 0)) by calling the account API that removes the ephemeral
marker (for example, acc.clear_ephemeral() or acc.set_ephemeral(false) depending
on the available method).

In `@programs/magicblock/src/ephemeral_accounts/process_create.rs`:
- Around line 44-50: The code currently only checks lamports and acc.ephemeral()
allowing zero-lamport accounts owned by other programs to be reinitialized;
update the validation after acquiring ephemeral (via
accounts::get_instruction_account_with_idx and acc) to also verify the account
owner is the system program (i.e., the account is system-owned) and if not
return InstructionError::InvalidAccountData, so only system-owned zero-lamport
accounts can be converted to ephemeral.
- Around line 37-41: Add a strict pubkey check for the vault: after fetching
vault via accounts::get_instruction_account_with_idx(transaction_context, 2)?
and after verifying owner() == id(), also compare the account's pubkey to the
canonical EPHEMERAL_VAULT_PUBKEY constant and return an appropriate
InstructionError if it doesn't match; apply the same pubkey equality validation
in the corresponding resize and close handlers so only the designated
EPHEMERAL_VAULT_PUBKEY can be used as the vault.

In `@programs/magicblock/src/ephemeral_accounts/process_resize.rs`:
- Around line 35-52: Add an ownership check before performing the resize: after
obtaining ephemeral via
accounts::get_instruction_account_with_idx(transaction_context, 1) and
confirming ephemeral.borrow().ephemeral(), verify that
ephemeral.borrow().owner() equals the current program id from the transaction
context (e.g., transaction_context.get_program_id() or equivalent accessor) and
return InstructionError::IllegalOwner or InstructionError::InvalidAccountOwner
if it does not match; only proceed with computing old_len/delta and
debiting/crediting when the owner check passes.

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

🤖 Fix all issues with AI agents
In `@programs/magicblock/src/ephemeral_accounts/process_create.rs`:
- Around line 36-41: Update the comment above the account check to correctly
state that the target account must be empty and owned by the system program —
replace "not owned by system program" with wording such as "owned by system
program"; the relevant code to locate is the
accounts::get_instruction_account_with_idx(...) call and the acc.lamports()/
*acc.owner() == system_program::ID check (variables ephemeral and acc).

In `@test-integration/Cargo.toml`:
- Line 107: The Cargo.toml now lists the dependency test-ephemeral-accounts but
the corresponding crate is missing; either create a new crate named
test-ephemeral-accounts (with a Cargo.toml and src/lib.rs/bin) in the workspace
and add "test-ephemeral-accounts" to the workspace members, ensuring the
dependency path matches the crate directory name, or remove the
test-ephemeral-accounts = { path = "test-ephemeral-accounts" } entry from the
test-integration Cargo.toml until the crate is implemented.

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 `@Cargo.toml`:
- Line 141: Replace the Git commit hash refs with the feature branch names for
the ephemeral accounts dependencies: change the solana-account dependency that
currently uses rev = "6eae52b" to use branch = "bmuddha/feat/ephemeral-flag",
and change the solana-svm dependency that uses rev = "bdbaac86" to use branch =
"bmuddha/feat/ephemeral-accounts"; update the Cargo.toml entries for the
solana-account and solana-svm dependencies accordingly so they reference the
named branches instead of commit hashes.

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

🤖 Fix all issues with AI agents
In `@programs/magicblock/src/ephemeral_accounts/validation.rs`:
- Around line 40-52: The doc comment for validate_sponsor is out of sync: the
function only checks that the sponsor (account index 0) is a signer, not
ownership for PDAs. Update the function doc above validate_sponsor to state that
the sponsor (index 0) — whether an on-curve key or a PDA — must be a signer
(PDAs may be signed via invoke_signed), and remove or replace the incorrect line
about PDA ownership; keep references to validate_sponsor, transaction_context,
get_current_instruction_context, and is_instruction_account_signer to locate the
code.
- Around line 15-37: InstructionContextFrames::try_from currently calls
.expect(...) on current_frame_idx which can panic; change that to return a
proper InstructionError instead of panicking: in the try_from implementation
(the code that sets current_frame_idx) replace the .expect(...) usage on the
current_frame_idx variable with logic that returns
Err(InstructionError::InvalidAccountData) (or another appropriate
InstructionError) when the index is None, so callers like get_caller_program_id
and validate_cpi_only that use InstructionContextFrames::try_from will receive
an error rather than causing a panic.

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

🤖 Fix all issues with AI agents
In `@programs/magicblock/src/ephemeral_accounts/validation.rs`:
- Around line 26-38: In validate_cpi_only, replace the manual
InstructionContextFrames::try_from(...) +
find_program_id_of_parent_of_current_instruction() check with a call to the
existing get_caller_program_id(transaction_context) helper: call
get_caller_program_id(transaction_context) and if it returns None return
Err(InstructionError::IncorrectProgramId), otherwise Ok(()). This removes
duplication and reuses the shared logic encapsulated by get_caller_program_id.

In `@programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs`:
- Around line 110-114: Add a log message where the code skips ephemeral
accounts: inside the loop guarding account.borrow().ephemeral() in
process_mutate_accounts (the block that currently does "if
account.borrow().ephemeral() { continue; }"), call ic_msg! to record that an
ephemeral account was encountered and skipped and include an identifying value
from the account (e.g., account id/address or a debug string via
account.borrow()) so the log shows which account was skipped for traceability.

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

🤖 Fix all issues with AI agents
In `@programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs`:
- Around line 110-121: The code currently skips ephemeral accounts via the
continue inside process_mutate_accounts (the account.borrow().ephemeral()
branch) but does not remove their entry from account_mods, leaving stale entries
and breaking the accounts_to_mod_len == account_mods_len invariant; fix by
removing the ephemeral account's modification from account_mods (call
account_mods.remove(&account_key) or account_mods.remove(account_key) using the
key obtained from transaction_context.get_key_of_account_at_index) before
continuing so bookkeeping stays consistent—alternatively, if ephemeral accounts
should never be present, replace the silent continue with an explicit error
return (e.g., Err(...)) to fail loudly.

In `@test-kit/src/lib.rs`:
- Around line 265-283: Extract the duplicated payer-selection logic into a
private helper (e.g., fn next_payer(&self) -> &Keypair) that performs the
fetch_add on self.payer_index and returns &self.payers[index %
self.payers.len()]; then replace the inline payer selection in both
build_transaction and build_transaction_with_signers with a call to
next_payer(), ensuring both functions still use the returned payer for signers
and for Transaction::new_signed_with_payer.

Comment on lines +265 to +283
/// Builds a transaction with additional signers beyond the payer.
pub fn build_transaction_with_signers(
&self,
ixs: &[Instruction],
additional_signers: &[&Keypair],
) -> Transaction {
let payer = {
let index = self.payer_index.fetch_add(1, Ordering::Relaxed);
&self.payers[index % self.payers.len()]
};
let mut signers: Vec<&Keypair> = vec![payer];
signers.extend(additional_signers);
Transaction::new_signed_with_payer(
ixs,
Some(&payer.pubkey()),
&signers,
self.ledger.latest_blockhash(),
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting payer selection to reduce duplication with build_transaction.

The payer-index logic (lines 271-273) is duplicated from build_transaction (lines 253-255). A small private helper (e.g., next_payer(&self) -> &Keypair) would keep both methods in sync.

♻️ Suggested helper extraction
+    fn next_payer(&self) -> &Keypair {
+        let index = self.payer_index.fetch_add(1, Ordering::Relaxed);
+        &self.payers[index % self.payers.len()]
+    }
+
     pub fn build_transaction(&self, ixs: &[Instruction]) -> Transaction {
-        let payer = {
-            let index = self.payer_index.fetch_add(1, Ordering::Relaxed);
-            &self.payers[index % self.payers.len()]
-        };
+        let payer = self.next_payer();
         Transaction::new_signed_with_payer(
             ixs,
             Some(&payer.pubkey()),
             &[payer],
             self.ledger.latest_blockhash(),
         )
     }

     pub fn build_transaction_with_signers(
         &self,
         ixs: &[Instruction],
         additional_signers: &[&Keypair],
     ) -> Transaction {
-        let payer = {
-            let index = self.payer_index.fetch_add(1, Ordering::Relaxed);
-            &self.payers[index % self.payers.len()]
-        };
+        let payer = self.next_payer();
         let mut signers: Vec<&Keypair> = vec![payer];
         signers.extend(additional_signers);
         Transaction::new_signed_with_payer(
🤖 Prompt for AI Agents
In `@test-kit/src/lib.rs` around lines 265 - 283, Extract the duplicated
payer-selection logic into a private helper (e.g., fn next_payer(&self) ->
&Keypair) that performs the fetch_add on self.payer_index and returns
&self.payers[index % self.payers.len()]; then replace the inline payer selection
in both build_transaction and build_transaction_with_signers with a call to
next_payer(), ensuring both functions still use the returned payer for signers
and for Transaction::new_signed_with_payer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not related to the PR

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

🤖 Fix all issues with AI agents
In `@programs/magicblock/src/ephemeral_accounts/process_create.rs`:
- Line 33: The unchecked cast "rent as i64" in the transfer call
(transfer_rent(transaction_context, rent as i64) in process_create.rs) can wrap
if rent grows; replace the cast with a fallible conversion using
i64::try_from(rent).map_err(|_| InstructionError::ArithmeticOverflow)? and pass
that result into transfer_rent. Apply the same pattern in process_close.rs and
process_resize.rs where rent (or any u64 → i64) is cast, using try_from and
mapping failures to InstructionError::ArithmeticOverflow to avoid silent
negative values.

In `@programs/magicblock/src/ephemeral_accounts/validation.rs`:
- Around line 42-49: The validate_vault function currently returns
InstructionError::InvalidAccountOwner when the vault pubkey doesn't match, which
is semantically misleading; change the returned error to
InstructionError::InvalidAccountData (or a domain-specific custom error) in
validate_vault (referencing validate_vault, VAULT_IDX, and
EPHEMERAL_VAULT_PUBKEY) so the mismatch is treated as bad data/identity rather
than ownership, and update any tests or upstream callers that rely on the
previous error variant to expect the new error.

@bmuddha bmuddha force-pushed the bmuddha/feat/ephemeral-account branch from de42924 to 9b65a26 Compare February 7, 2026 05:30
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: 6

🤖 Fix all issues with AI agents
In `@magicblock-accounts-db/src/lib.rs`:
- Around line 172-176: The removal check in the upsert path compares
account.owner() to Pubkey::default(), but the close processor in
process_close.rs sets the owner to system_program::id(), so closed ephemeral
accounts are not removed; update the condition in the upsert/remove branch (the
block that calls self.index.remove(pubkey, txn!())) to compare account.owner()
against system_program::id() when account.ephemeral() is true, or alternatively
change the close processor in process_close.rs to set the owner to
Pubkey::default(); pick one approach and make the owner comparison and removal
behavior consistent between the close processor and the code that calls
self.index.remove.

In `@magicblock-processor/tests/ephemeral_accounts.rs`:
- Around line 960-991: The test test_pda_wrong_owner_fails currently creates a
PDA account with 0 lamports so the transaction might fail due to insufficient
funds instead of the owner check; update the account creation that uses
Pubkey::find_program_address and AccountSharedData::new/insert_account to give
the PDA a nonzero balance (e.g., 1_000_000_000 lamports) before inserting it
into env.accountsdb so the failure path exercised by env.execute_transaction and
the assertion targets the ownership validation rather than funding.
- Around line 706-741: Update test_resize_to_zero_size to assert lamport
conservation like other resize tests: capture sponsor and vault balances before
the resize (use env.get_payer().pubkey and EPHEMERAL_VAULT_PUBKEY via
env.get_account), perform the resize using resize_ephemeral_account_ix, then
assert the sponsor's lamports increased by the rent-exempt amount freed for the
original 1000-byte account and the vault's lamports decreased by the same amount
so total lamports are conserved; also assert ephemeral_after.data().len() == 0
as before. Use the same balance-check logic/helpers used in
test_resize_smaller_via_cpi to compute the expected refund/debit amounts so the
zero-size edge case is validated consistently.
- Around line 875-906: The test test_insufficient_balance_fails is missing the
delegated sponsor setup so it may fail for the wrong reason; call
init_sponsor(&env, sponsor) before constructing the
create_ephemeral_account_ix/transaction to ensure the payer is marked as
delegated and the failure (assert result.is_err()) is due to insufficient
balance. Make sure you still set the sponsor lamports to 100 (via
sponsor_acc.set_lamports(100); sponsor_acc.commit();) and then call
init_sponsor(&env, sponsor) so the sponsorship state is initialized while
preserving the low balance before building and executing the transaction.
- Around line 1018-1072: In test_full_lifecycle, capture the initial lamports
for the sponsor and EPHEMERAL_VAULT_PUBKEY (use env.get_account or
env.try_get_account) before calling create_ephemeral_account_ix, then after
executing the close_ephemeral_account_ix assert that the sponsor's lamports
equal the captured initial sponsor lamports and that the vault's lamports are
restored (or unchanged) as expected; update references around
ExecutionTestEnv::new_with_config, init_vault, init_sponsor,
create_ephemeral_account_ix, close_ephemeral_account_ix, and env.try_get_account
to read balances before the create step and perform the equality assertions
after the close step.

In `@programs/magicblock/src/ephemeral_accounts/mod.rs`:
- Around line 63-77: In transfer_rent, avoid doing unnecessary account ops when
amount == 0 by adding an early return at the start of the function: check if
amount == 0 and return Ok(()) before computing abs or calling
accounts::debit_instruction_account_at_index /
accounts::credit_instruction_account_at_index for SPONSOR_IDX and VAULT_IDX;
this prevents redundant account lookups/writes when resize produces delta == 0.

Comment on lines +172 to +176
// The ephemeral account has been closed, remove it from DB
if account.ephemeral() && account.owner() == &Pubkey::default() {
self.index.remove(pubkey, txn!())?;
return Ok(());
}
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 | 🔴 Critical

Ephemeral account removal condition won't match the close processor's output.

The close processor in process_close.rs sets the owner to system_program::id(), but this check expects Pubkey::default(). Closed ephemeral accounts will never hit this removal path — they'll instead fall through to the normal upsert logic, persisting a stale zero-lamport entry.

Either align this condition with what the close processor actually writes, or update the close processor to set Pubkey::default() as the owner.

-        if account.ephemeral() && account.owner() == &Pubkey::default() {
+        if account.ephemeral() && account.owner() == &solana_sdk_ids::system_program::id() {
🤖 Prompt for AI Agents
In `@magicblock-accounts-db/src/lib.rs` around lines 172 - 176, The removal check
in the upsert path compares account.owner() to Pubkey::default(), but the close
processor in process_close.rs sets the owner to system_program::id(), so closed
ephemeral accounts are not removed; update the condition in the upsert/remove
branch (the block that calls self.index.remove(pubkey, txn!())) to compare
account.owner() against system_program::id() when account.ephemeral() is true,
or alternatively change the close processor in process_close.rs to set the owner
to Pubkey::default(); pick one approach and make the owner comparison and
removal behavior consistent between the close processor and the code that calls
self.index.remove.

Comment on lines +706 to +741
#[tokio::test]
async fn test_resize_to_zero_size() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);

let sponsor = env.get_payer().pubkey;
init_sponsor(&env, sponsor);
let ephemeral = env.create_account(0);

// Create with initial size
let ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
1000,
);
let txn = env.build_transaction_with_signers(&[ix], &[&ephemeral]);
assert!(env.execute_transaction(txn).await.is_ok());

// Resize to zero
let ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
0,
);
let txn = env.build_transaction(&[ix]);

let result = env.execute_transaction(txn).await;
assert!(result.is_ok(), "Should allow resize to zero");

let ephemeral_after = env.get_account(ephemeral.pubkey());
assert_eq!(ephemeral_after.data().len(), 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

test_resize_to_zero_size lacks lamport conservation checks.

Other resize tests (e.g., test_resize_smaller_via_cpi) thoroughly verify that sponsor gets refunded, vault is debited, and total lamports are conserved. This test only checks data().len() == 0 but doesn't verify that the full rent for the original 1000 bytes was refunded to the sponsor and debited from the vault.

Consider adding the same conservation/balance assertions for consistency and to catch potential rent-accounting bugs on the zero-size edge case.

🤖 Prompt for AI Agents
In `@magicblock-processor/tests/ephemeral_accounts.rs` around lines 706 - 741,
Update test_resize_to_zero_size to assert lamport conservation like other resize
tests: capture sponsor and vault balances before the resize (use
env.get_payer().pubkey and EPHEMERAL_VAULT_PUBKEY via env.get_account), perform
the resize using resize_ephemeral_account_ix, then assert the sponsor's lamports
increased by the rent-exempt amount freed for the original 1000-byte account and
the vault's lamports decreased by the same amount so total lamports are
conserved; also assert ephemeral_after.data().len() == 0 as before. Use the same
balance-check logic/helpers used in test_resize_smaller_via_cpi to compute the
expected refund/debit amounts so the zero-size edge case is validated
consistently.

Comment on lines +875 to +906
#[tokio::test]
async fn test_insufficient_balance_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);

// Use the payer but give it very low balance
let sponsor = env.get_payer().pubkey;

// Set sponsor balance to very low amount
let mut sponsor_acc = env.get_account(sponsor);
sponsor_acc.set_lamports(100); // Only 100 lamports
sponsor_acc.commit();

let ephemeral = env.create_account(0);

let data_len = 1000;
let required_rent = rent_for(data_len);

assert!(required_rent > 100, "Rent should exceed sponsor balance");

let ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
data_len,
);
let txn = env.build_transaction_with_signers(&[ix], &[&ephemeral]);

let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - insufficient balance");
}
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

Missing init_sponsor — test may pass for the wrong reason.

Every other test that uses the payer as sponsor calls init_sponsor(&env, sponsor) to mark the account as delegated. This test skips that step, so the transaction may fail because the sponsor is not delegated rather than because of insufficient balance. The assertion result.is_err() would still pass, but the test wouldn't be validating what it claims to.

Proposed fix
     // Set sponsor balance to very low amount
     let mut sponsor_acc = env.get_account(sponsor);
     sponsor_acc.set_lamports(100); // Only 100 lamports
+    sponsor_acc.set_delegated(true);
     sponsor_acc.commit();
📝 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
#[tokio::test]
async fn test_insufficient_balance_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
// Use the payer but give it very low balance
let sponsor = env.get_payer().pubkey;
// Set sponsor balance to very low amount
let mut sponsor_acc = env.get_account(sponsor);
sponsor_acc.set_lamports(100); // Only 100 lamports
sponsor_acc.commit();
let ephemeral = env.create_account(0);
let data_len = 1000;
let required_rent = rent_for(data_len);
assert!(required_rent > 100, "Rent should exceed sponsor balance");
let ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
data_len,
);
let txn = env.build_transaction_with_signers(&[ix], &[&ephemeral]);
let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - insufficient balance");
}
#[tokio::test]
async fn test_insufficient_balance_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
// Use the payer but give it very low balance
let sponsor = env.get_payer().pubkey;
// Set sponsor balance to very low amount
let mut sponsor_acc = env.get_account(sponsor);
sponsor_acc.set_lamports(100); // Only 100 lamports
sponsor_acc.set_delegated(true);
sponsor_acc.commit();
let ephemeral = env.create_account(0);
let data_len = 1000;
let required_rent = rent_for(data_len);
assert!(required_rent > 100, "Rent should exceed sponsor balance");
let ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
data_len,
);
let txn = env.build_transaction_with_signers(&[ix], &[&ephemeral]);
let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - insufficient balance");
}
🤖 Prompt for AI Agents
In `@magicblock-processor/tests/ephemeral_accounts.rs` around lines 875 - 906, The
test test_insufficient_balance_fails is missing the delegated sponsor setup so
it may fail for the wrong reason; call init_sponsor(&env, sponsor) before
constructing the create_ephemeral_account_ix/transaction to ensure the payer is
marked as delegated and the failure (assert result.is_err()) is due to
insufficient balance. Make sure you still set the sponsor lamports to 100 (via
sponsor_acc.set_lamports(100); sponsor_acc.commit();) and then call
init_sponsor(&env, sponsor) so the sponsorship state is initialized while
preserving the low balance before building and executing the transaction.

Comment on lines +960 to +991
#[tokio::test]
async fn test_pda_wrong_owner_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);

// Create a PDA owned by system program (not guinea)
let (pda, _bump) = Pubkey::find_program_address(
&[b"wrong"],
&solana_sdk_ids::system_program::id(),
);
let mut account =
AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
account.set_delegated(true);
let _ = env.accountsdb.insert_account(&pda, &account);

let ephemeral = env.create_account(0);

let ix = Instruction::new_with_bincode(
guinea::ID,
&GuineaInstruction::CreateEphemeralAccount { data_len: 1000 },
vec![
AccountMeta::new_readonly(magicblock_magic_program_api::ID, false),
AccountMeta::new(pda, false), // NOT a signer, not owned by guinea
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_PUBKEY, false),
],
);
let txn = env.build_transaction(&[ix]);

let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - PDA not owned by caller");
}
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

Verify the PDA wrong-owner test exercises the right validation path.

The PDA is derived from &[b"wrong"] with system_program::id() as the program, and the account is created with 0 lamports. Since the sponsor has no balance, the test could also fail due to insufficient funds rather than the owner check. Consider giving the PDA account a sufficient balance (e.g., 1_000_000_000) to ensure the test isolates the ownership validation.

Proposed fix
     let mut account =
-        AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
+        AccountSharedData::new(1_000_000_000, 0, &solana_sdk_ids::system_program::id());
     account.set_delegated(true);
📝 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
#[tokio::test]
async fn test_pda_wrong_owner_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
// Create a PDA owned by system program (not guinea)
let (pda, _bump) = Pubkey::find_program_address(
&[b"wrong"],
&solana_sdk_ids::system_program::id(),
);
let mut account =
AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
account.set_delegated(true);
let _ = env.accountsdb.insert_account(&pda, &account);
let ephemeral = env.create_account(0);
let ix = Instruction::new_with_bincode(
guinea::ID,
&GuineaInstruction::CreateEphemeralAccount { data_len: 1000 },
vec![
AccountMeta::new_readonly(magicblock_magic_program_api::ID, false),
AccountMeta::new(pda, false), // NOT a signer, not owned by guinea
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_PUBKEY, false),
],
);
let txn = env.build_transaction(&[ix]);
let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - PDA not owned by caller");
}
#[tokio::test]
async fn test_pda_wrong_owner_fails() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
// Create a PDA owned by system program (not guinea)
let (pda, _bump) = Pubkey::find_program_address(
&[b"wrong"],
&solana_sdk_ids::system_program::id(),
);
let mut account =
AccountSharedData::new(1_000_000_000, 0, &solana_sdk_ids::system_program::id());
account.set_delegated(true);
let _ = env.accountsdb.insert_account(&pda, &account);
let ephemeral = env.create_account(0);
let ix = Instruction::new_with_bincode(
guinea::ID,
&GuineaInstruction::CreateEphemeralAccount { data_len: 1000 },
vec![
AccountMeta::new_readonly(magicblock_magic_program_api::ID, false),
AccountMeta::new(pda, false), // NOT a signer, not owned by guinea
AccountMeta::new(ephemeral.pubkey(), false),
AccountMeta::new(EPHEMERAL_VAULT_PUBKEY, false),
],
);
let txn = env.build_transaction(&[ix]);
let result = env.execute_transaction(txn).await;
assert!(result.is_err(), "Should fail - PDA not owned by caller");
}
🤖 Prompt for AI Agents
In `@magicblock-processor/tests/ephemeral_accounts.rs` around lines 960 - 991, The
test test_pda_wrong_owner_fails currently creates a PDA account with 0 lamports
so the transaction might fail due to insufficient funds instead of the owner
check; update the account creation that uses Pubkey::find_program_address and
AccountSharedData::new/insert_account to give the PDA a nonzero balance (e.g.,
1_000_000_000 lamports) before inserting it into env.accountsdb so the failure
path exercised by env.execute_transaction and the assertion targets the
ownership validation rather than funding.

Comment on lines +1018 to +1072
#[tokio::test]
async fn test_full_lifecycle() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);

let sponsor = env.get_payer().pubkey;
init_sponsor(&env, sponsor);
let ephemeral = env.create_account(0);

// Create → Resize (grow) → Resize (shrink) → Close
let create_ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
1000,
);
let txn = env.build_transaction_with_signers(&[create_ix], &[&ephemeral]);
assert!(env.execute_transaction(txn).await.is_ok());

let grow_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
2000,
);
let txn = env.build_transaction(&[grow_ix]);
assert!(env.execute_transaction(txn).await.is_ok());

let shrink_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
500,
);
let txn = env.build_transaction(&[shrink_ix]);
assert!(env.execute_transaction(txn).await.is_ok());

let close_ix = close_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
);
let txn = env.build_transaction(&[close_ix]);
assert!(env.execute_transaction(txn).await.is_ok());

// Closed ephemeral accounts are removed from the DB
assert!(
env.try_get_account(ephemeral.pubkey()).is_none(),
"Closed ephemeral account should be removed from DB"
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding end-to-end balance check in full lifecycle test.

This lifecycle test (create → grow → shrink → close) correctly verifies the account is removed from DB, but doesn't check that the sponsor's final balance equals the initial balance (since all rent should be refunded on close). Adding a single conservation assertion at the end would strengthen this as a regression test for the complete flow.

Suggestion
+    // After full lifecycle, sponsor should have all rent refunded
+    let sponsor_final = env.get_account(sponsor);
+    let vault_final = env.get_account(EPHEMERAL_VAULT_PUBKEY);
+    // Capture sponsor_initial and vault_initial before the create step above
+    // and assert conservation here
+
     // Closed ephemeral accounts are removed from the DB
     assert!(
         env.try_get_account(ephemeral.pubkey()).is_none(),
         "Closed ephemeral account should be removed from DB"
     );

You'd need to capture sponsor and vault lamports before the create step and assert equality at the end.

📝 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
#[tokio::test]
async fn test_full_lifecycle() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
let sponsor = env.get_payer().pubkey;
init_sponsor(&env, sponsor);
let ephemeral = env.create_account(0);
// Create → Resize (grow) → Resize (shrink) → Close
let create_ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
1000,
);
let txn = env.build_transaction_with_signers(&[create_ix], &[&ephemeral]);
assert!(env.execute_transaction(txn).await.is_ok());
let grow_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
2000,
);
let txn = env.build_transaction(&[grow_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
let shrink_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
500,
);
let txn = env.build_transaction(&[shrink_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
let close_ix = close_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
);
let txn = env.build_transaction(&[close_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
// Closed ephemeral accounts are removed from the DB
assert!(
env.try_get_account(ephemeral.pubkey()).is_none(),
"Closed ephemeral account should be removed from DB"
);
}
#[tokio::test]
async fn test_full_lifecycle() {
let env = ExecutionTestEnv::new_with_config(0, 1, false);
init_vault(&env);
let sponsor = env.get_payer().pubkey;
init_sponsor(&env, sponsor);
let ephemeral = env.create_account(0);
// Capture initial balances before lifecycle operations
let sponsor_initial = env.get_account(sponsor).lamports;
let vault_initial = env.get_account(EPHEMERAL_VAULT_PUBKEY).lamports;
// Create → Resize (grow) → Resize (shrink) → Close
let create_ix = create_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
1000,
);
let txn = env.build_transaction_with_signers(&[create_ix], &[&ephemeral]);
assert!(env.execute_transaction(txn).await.is_ok());
let grow_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
2000,
);
let txn = env.build_transaction(&[grow_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
let shrink_ix = resize_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
500,
);
let txn = env.build_transaction(&[shrink_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
let close_ix = close_ephemeral_account_ix(
magicblock_magic_program_api::ID,
sponsor,
ephemeral.pubkey(),
EPHEMERAL_VAULT_PUBKEY,
);
let txn = env.build_transaction(&[close_ix]);
assert!(env.execute_transaction(txn).await.is_ok());
// Closed ephemeral accounts are removed from the DB
assert!(
env.try_get_account(ephemeral.pubkey()).is_none(),
"Closed ephemeral account should be removed from DB"
);
// After full lifecycle, sponsor and vault should maintain balance conservation
let sponsor_final = env.get_account(sponsor).lamports;
let vault_final = env.get_account(EPHEMERAL_VAULT_PUBKEY).lamports;
assert_eq!(sponsor_initial, sponsor_final, "Sponsor balance should be conserved after full lifecycle");
assert_eq!(vault_initial, vault_final, "Vault balance should be conserved after full lifecycle");
}
🤖 Prompt for AI Agents
In `@magicblock-processor/tests/ephemeral_accounts.rs` around lines 1018 - 1072,
In test_full_lifecycle, capture the initial lamports for the sponsor and
EPHEMERAL_VAULT_PUBKEY (use env.get_account or env.try_get_account) before
calling create_ephemeral_account_ix, then after executing the
close_ephemeral_account_ix assert that the sponsor's lamports equal the captured
initial sponsor lamports and that the vault's lamports are restored (or
unchanged) as expected; update references around
ExecutionTestEnv::new_with_config, init_vault, init_sponsor,
create_ephemeral_account_ix, close_ephemeral_account_ix, and env.try_get_account
to read balances before the create step and perform the equality assertions
after the close step.

Comment on lines +63 to +77
fn transfer_rent(
tc: &TransactionContext,
amount: i64,
) -> Result<(), InstructionError> {
if amount > 0 {
let abs = amount as u64;
accounts::debit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::credit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
} else {
let abs = amount.unsigned_abs();
accounts::credit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::debit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
}
Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

transfer_rent silently no-ops when amount == 0.

When amount is exactly 0, the else branch calls credit/debit with 0 lamports. This is harmless but does unnecessary account lookups and writes. Consider adding an early return for the zero case, especially since resize can produce delta == 0.

Optional: skip zero transfers
 fn transfer_rent(
     tc: &TransactionContext,
     amount: i64,
 ) -> Result<(), InstructionError> {
+    if amount == 0 {
+        return Ok(());
+    }
     if amount > 0 {
📝 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
fn transfer_rent(
tc: &TransactionContext,
amount: i64,
) -> Result<(), InstructionError> {
if amount > 0 {
let abs = amount as u64;
accounts::debit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::credit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
} else {
let abs = amount.unsigned_abs();
accounts::credit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::debit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
}
Ok(())
}
fn transfer_rent(
tc: &TransactionContext,
amount: i64,
) -> Result<(), InstructionError> {
if amount == 0 {
return Ok(());
}
if amount > 0 {
let abs = amount as u64;
accounts::debit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::credit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
} else {
let abs = amount.unsigned_abs();
accounts::credit_instruction_account_at_index(tc, SPONSOR_IDX, abs)?;
accounts::debit_instruction_account_at_index(tc, VAULT_IDX, abs)?;
}
Ok(())
}
🤖 Prompt for AI Agents
In `@programs/magicblock/src/ephemeral_accounts/mod.rs` around lines 63 - 77, In
transfer_rent, avoid doing unnecessary account ops when amount == 0 by adding an
early return at the start of the function: check if amount == 0 and return
Ok(()) before computing abs or calling
accounts::debit_instruction_account_at_index /
accounts::credit_instruction_account_at_index for SPONSOR_IDX and VAULT_IDX;
this prevents redundant account lookups/writes when resize produces delta == 0.

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.

feat: implement ephemeral accounts

1 participant