Skip to content

[Low] ExecuteRedemption reclaims the redeemer's PDA rent to the cranker, while CancelRedemption returns it to the redeemer #412

Description

@0x-SquidSol

Summary

The redeemer funds the redemption PDA's rent, and the cranker keeps it.

RequestRedeemLpShares creates the PDA with the redeemer as payer (src/v16_program.rs:14947):

&system_instruction::create_account(
    redeemer.key,
    ...
    rent.minimum_balance(rlen),

ExecuteRedemption consumes the PDA and credits those lamports to cranker, which is accounts[0] and is only expect_signer'd — never compared against the redeemer (src/v16_program.rs:15846):

let reclaim = redemption_ai.lamports();
**redemption_ai.try_borrow_mut_lamports()? = 0;
**cranker.try_borrow_mut_lamports()? = cranker.lamports().checked_add(reclaim)...

CancelRedemption handles the same lamports differently (src/v16_program.rs:15951):

// ── Consume the redemption PDA (zero magic — replay guard) + reclaim rent
//    to the redeemer (the original rent payer at request time). ──
**redeemer.try_borrow_mut_lamports()? = redeemer.lamports().checked_add(reclaim)...

So the two consuming paths disagree about who owns the rent, and the cancel path names the redeemer as "the original rent payer at request time".

Reproduction

Added to tests/v16_fork_lp_vault_redeem.rs. Passes against main at 19d5d93:

cargo test --test v16_fork_lp_vault_redeem execute_redemption_pays_the_redeemers_rent
test execute_redemption_pays_the_redeemers_rent_to_the_cranker ... ok
test result: ok. 1 passed; 0 failed

It requests a redemption, records the PDA's rent and both balances, executes, and asserts the PDA is drained, the redeemer's balance is unchanged, and the cranker is better off. No new scaffolding was needed — the suite's existing execute helper already signs as env.payer rather than the depositor, so every redemption in these tests is already cranked by a third party.

Assessment

Low. The amount is one account's rent exemption per redemption, and no principal is at risk: the payout itself is pinned to the recorded redemption.redeemer's token account, so a third-party cranker cannot redirect funds.

There is a second-order effect worth separating from the rent. Because execute takes no authority, anyone may run a redemption the moment its cooldown elapses, which settles it at that slot's NAV and removes the redeemer's option to cancel and wait. That is a consequence of the path being permissionless rather than a defect in itself, but it means the redeemer neither controls the timing nor recovers the rent.

We are not assuming this is an oversight. ExecuteRedemption's twelve accounts do not include the redeemer's wallet — accounts[9] is their token account — so there is no system account available to refund. Paying the party who submits a permissionless instruction is also a reasonable incentive on its face. Both are defensible reasons for the current behaviour.

What prompted the report is that nothing says so. There is no comment on the execute path explaining the cranker payment as intentional, while the cancel path explicitly identifies the redeemer as the rightful recipient of the same lamports. As written, the code reads as two paths that were reasoned about independently rather than one deliberate policy.

Suggested direction

If the payment is intended as a crank bounty, we would suggest documenting it at the reclaim site, in the same way the cancel path documents its choice. That alone resolves the discrepancy.

If instead the redeemer is meant to be made whole, there are two shapes, neither free:

  • Credit redeemer_dest. It is already in the account list and already owned by the redeemer, who can recover the lamports by closing it. No instruction-layout change, but it leaves lamports in a token account, which is unusual.
  • Add the redeemer's wallet to the account list. Cleaner semantically, but it is a breaking layout change for every client that builds this instruction.

Given the amounts, we would not suggest the second unless the account list is being revised for other reasons.

Environment

  • dcccrypto/percolator-prog main at 19d5d93
  • Engine dcccrypto/percolator main at b5ddba2
  • cargo test --test v16_fork_lp_vault_redeem, rustc 1.95.0

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions