Skip to content

fix: fixed undelegation of ephemeral balance #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

taco-paco
Copy link
Contributor

@taco-paco taco-paco commented May 6, 2025

⚠️ 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 Bug Yes Link

Problem

Ephemeral balance was undelegated to delegation program itslef. Expected behavior delegation to system program.

Solution

Introduced new method undelegate_ephemeral_balance that will call undelegate and then reassign ownership to system_program

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)

Added v1 for CloseEphemeralBalance. Reason is that this change shall be backwards-compatible. For exmaple, some users may have accounts undelegated using old logic, their lamports will be stuck if we remove old CloseEphemeralBalance.

We keep only one version of CloseEphemeralBalance in InstructionBuilder utilities in order to enforce a newer logic. Clients may still recreate old instruction and call "v0" of CloseEphemeralBalance.

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

Greptile Summary

This PR fixes the undelegation of ephemeral balances by ensuring ownership is properly transferred to the system program instead of the delegation program.

  • Added new src/instruction_builder/undelegate_ephemeral_balance.rs to handle proper ownership reassignment to system program after undelegation
  • Added CloseEphemeralBalanceV1 discriminator (0x10) while maintaining backward compatibility with v0 for existing accounts
  • Added comprehensive test coverage in tests/test_top_up.rs to verify undelegation and closing functionality
  • Modified src/processor/close_ephemeral_balance_v1.rs to properly verify system program ownership and handle lamport transfers
  • Updated instruction builders to enforce new undelegation logic while allowing clients to still access v0 if needed

The changes look well-structured with proper error handling and test coverage. The backward compatibility approach for CloseEphemeralBalance is appropriate to prevent breaking existing accounts.

💡 (5/5) You can turn off certain types of comments like style here!

…em program. Added new version of close_ephemeral_balance to support it. kept old one for backward-compatability
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 file(s) reviewed, 5 comment(s)
Edit PR Review Bot Settings | Greptile

Copy link

@Dodecahedr0x Dodecahedr0x left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -45,7 +49,8 @@ impl DlpDiscriminator {
impl TryFrom<[u8; 8]> for DlpDiscriminator {
type Error = ProgramError;
fn try_from(bytes: [u8; 8]) -> Result<Self, Self::Error> {
match bytes[0] {
let discriminator = u64::from_le_bytes(bytes);

Choose a reason for hiding this comment

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

Isn't this more CU-intense without benefits (until the program reaches 255 instructions)?


use crate::discriminator::DlpDiscriminator;
use crate::pda::ephemeral_balance_pda_from_payer;

/// Creates instruction to close an ephemeral balance account
/// See [crate::processor::process_close_ephemeral_balance] for docs.
/// See [crate::processor::process_close_ephemeral_balance_v1] for docs.
/// [crate::processor::process_close_ephemeral_balance] now deprecated

Choose a reason for hiding this comment

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

Should probably annotate process_close_ephemeral_balance with #[deprecated(since="...", note="...")]

if ephemeral_balance_account.owner != &system_program::id() {
msg!(
"ephemeral balance expected to be owned by system program: {}",
system_program::id()

Choose a reason for hiding this comment

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

I think it would be more interesting to log ephemeral_balance_account.owner rather than the well-known system program. It might be even better to give a static message to avoid the CU cost of formatting strings

return Ok(());
}

let ephemeral_balance_bump_slice: &[u8] = &[ephemeral_balance_bump];

Choose a reason for hiding this comment

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

Shouldn't it use close_pda at some point like the old process_close_ephemeral_balance?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I understand this practically closes account, since it doesn't have lamports and data

@@ -56,7 +56,7 @@ pub fn process_top_up_ephemeral_balance(
create_pda(
ephemeral_balance_account,
&system_program::id(),
8,
0,

Choose a reason for hiding this comment

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

I don't understand what causes this change in this PR (and the subsequent changes in tests), I'm guessing it's the core of the fix for something introduced in another PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's 2 points to this

  1. It fixes undelegation. Otherwise we try to call non-existing callback
  2. I reckoned discriminator isn't needed. It is not set anyway. the account just stores lamports, the same as on curve addresses don't have any discriminator. The only thing that @GabrielePicco mentioned is that it might complicate filtering, but the address of this pda shall suffice for filtering

Copy link
Contributor

Choose a reason for hiding this comment

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

It was there mainly for consistency (discriminator should be set on creation) to allow queries such as getAllEscrowAccounts that are not otherwise possible.

Copy link
Contributor Author

@taco-paco taco-paco May 7, 2025

Choose a reason for hiding this comment

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

Ah I see now what you meant

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.

Unless I'm missing something, I think there is a major problem with this implementation, the ER state is not committed before undelegating. Undelegation should always originate from a CPI executed as part of a transaction in the ER, and should go trough the normal commit/finalize flow, as otherwise the undelegation will revert the original balance

],
data: [
DlpDiscriminator::CloseEphemeralBalance.to_vec(),
DlpDiscriminator::CloseEphemeralBalanceV1.to_vec(),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not currently used, so we can avoid the "V1" as it's not gonna be a breaking change

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.

3 participants