-
Notifications
You must be signed in to change notification settings - Fork 2
feat: rent recipient in forward_token #37
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
base: main
Are you sure you want to change the base?
Conversation
|
thanks @Frizellle ! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for specifying a custom rent recipient in the forward_token function. When forwarding tokens, the rent from the closed token account can now be sent to a specified account instead of always going to the sender.
Changes:
- Modified
forward_tokento accept an optional rent recipient via remaining accounts - Added validation to ensure rent recipient account is writable
- Added comprehensive test coverage for the new rent recipient functionality
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/solana-vm/programs/relay-forwarder/src/lib.rs | Implements rent recipient logic with validation and new error type |
| packages/solana-vm/tests/relay-forwarder.ts | Adds test case verifying rent recipient receives lamports from closed account |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| seeds, | ||
| ); | ||
| ) |
Copilot
AI
Jan 14, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Syntax error: missing semicolon and method call. The line should be split into two statements: closing the parenthesis for CpiContext::new_with_signer with ); on line 89, then calling close_account(close_account_cpi_ctx)?; on line 90.
| ) | |
| ); | |
| close_account(close_account_cpi_ctx)?; |
| const DepositEvent = events.find((event) => event.name === "depositEvent"); | ||
| assert.exists(DepositEvent); | ||
|
|
||
| assert.equal(DepositEvent?.data.amount.toNumber(), depositAmount); | ||
| assert.equal( | ||
| DepositEvent?.data.depositor.toBase58(), | ||
| depositor.publicKey.toBase58() | ||
| ); | ||
| assert.equal(DepositEvent?.data.id.toString(), id.toString()); | ||
| assert.equal(DepositEvent?.data.token.toBase58(), mint.toBase58()); |
Copilot
AI
Jan 14, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable name DepositEvent uses PascalCase which is typically reserved for classes/types in TypeScript. Consider renaming to depositEvent to follow conventional camelCase naming for variables.
| const DepositEvent = events.find((event) => event.name === "depositEvent"); | |
| assert.exists(DepositEvent); | |
| assert.equal(DepositEvent?.data.amount.toNumber(), depositAmount); | |
| assert.equal( | |
| DepositEvent?.data.depositor.toBase58(), | |
| depositor.publicKey.toBase58() | |
| ); | |
| assert.equal(DepositEvent?.data.id.toString(), id.toString()); | |
| assert.equal(DepositEvent?.data.token.toBase58(), mint.toBase58()); | |
| const depositEvent = events.find((event) => event.name === "depositEvent"); | |
| assert.exists(depositEvent); | |
| assert.equal(depositEvent?.data.amount.toNumber(), depositAmount); | |
| assert.equal( | |
| depositEvent?.data.depositor.toBase58(), | |
| depositor.publicKey.toBase58() | |
| ); | |
| assert.equal(depositEvent?.data.id.toString(), id.toString()); | |
| assert.equal(depositEvent?.data.token.toBase58(), mint.toBase58()); |
Security Audit ReportI've completed a thorough security review and found several concerning issues: 🔴 CRITICAL: Rent Theft via Front-RunningThe Issue: Attack Scenario: Code Location: let rent_fee_payer = ctx.remaining_accounts.get(0);
let destination_info: AccountInfo<'info> = match rent_fee_payer {
Some(account) => {
require!(account.is_writable, ForwarderError::RentRecipientMustBeWritable);
account.clone() // ⚠️ No validation of ownership or authority
}
None => ctx.accounts.sender.to_account_info(),
};🟡 HIGH: Missing Rent Recipient ValidationThe Issue:
Risks:
Recommendation:
🟡 MEDIUM: No Access Control on forward_tokenPre-existing Issue (made worse by PR): Before PR: Rent goes to the caller (potentially unfair but deterministic) Attack Variations:
🟢 LOW: Missing Test CoverageThe test only covers the happy path. Missing security-critical tests for:
Recommended FixesOption 1: Require Rent Recipient to be Sender (Conservative)let destination_info: AccountInfo<'info> = match rent_fee_payer {
Some(account) => {
require!(account.is_writable, ForwarderError::RentRecipientMustBeWritable);
require!(
account.key() == ctx.accounts.sender.key(),
ForwarderError::RentRecipientMustBeSender
);
account.clone()
}
None => ctx.accounts.sender.to_account_info(),
};Option 2: Require Rent Recipient Signature (More Flexible)let destination_info: AccountInfo<'info> = match rent_fee_payer {
Some(account) => {
require!(account.is_writable, ForwarderError::RentRecipientMustBeWritable);
require!(account.is_signer, ForwarderError::RentRecipientMustSign);
account.clone()
}
None => ctx.accounts.sender.to_account_info(),
};Option 3: Add Access Control to forward_tokenTrack who created the forwarder_token_account and only allow them to call forward_token: // Add to ForwardToken struct:
pub struct ForwardToken<'info> {
// ... existing fields ...
/// The original creator/funder of the forwarder token account
#[account(
constraint = creator.key() == forwarder_token_account.get_creator()
@ ForwarderError::UnauthorizedSender
)]
pub creator: Signer<'info>,
}(Note: This requires tracking the creator, which isn't currently stored) SummaryVERDICT: ❌ DO NOT MERGE without fixes The PR introduces a critical rent theft vulnerability by allowing arbitrary rent redirection without authorization checks. The most practical fix is Option 1 - require the rent recipient to be the sender. This maintains the intended functionality (allowing senders to specify where their rent goes) while preventing theft. Impact Assessment:
Questions for Clarification:
|
No description provided.