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
Summary
The redeemer funds the redemption PDA's rent, and the cranker keeps it.
RequestRedeemLpSharescreates the PDA with the redeemer as payer (src/v16_program.rs:14947):ExecuteRedemptionconsumes the PDA and credits those lamports tocranker, which isaccounts[0]and is onlyexpect_signer'd — never compared against the redeemer (src/v16_program.rs:15846):CancelRedemptionhandles the same lamports differently (src/v16_program.rs:15951):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 againstmainat19d5d93: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
executehelper already signs asenv.payerrather 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:
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.Given the amounts, we would not suggest the second unless the account list is being revised for other reasons.
Environment
dcccrypto/percolator-progmainat19d5d93dcccrypto/percolatormainatb5ddba2cargo test --test v16_fork_lp_vault_redeem, rustc 1.95.0