Skip to content
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

program: reclaim rent #763

Merged
merged 7 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add ability to reclaim rent without deleting account ([#763](https://github.com/drift-labs/protocol-v2/pull/763))
- program: add borrow explanation to DepositRecords ([#772](https://github.com/drift-labs/protocol-v2/pull/772))
- sdk: OrderSubscriber has resync option ([#780](https://github.com/drift-labs/protocol-v2/pull/780))
- program: only consider recent last_active_slot in qualifies_for_withdraw_feen ([#756](https://github.com/drift-labs/protocol-v2/pull/756))
Expand Down
2 changes: 2 additions & 0 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ pub enum ErrorCode {
InvalidMarginCalculation,
#[msg("CantPayUserInitFee")]
CantPayUserInitFee,
#[msg("CantReclaimRent")]
CantReclaimRent,
}

#[macro_export]
Expand Down
59 changes: 58 additions & 1 deletion programs/drift/src/instructions/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use crate::instructions::optional_accounts::{
AccountMaps,
};
use crate::instructions::SpotFulfillmentType;
use crate::load;
use crate::load_mut;
use crate::math::casting::Cast;
use crate::math::liquidation::is_user_being_liquidated;
Expand Down Expand Up @@ -66,6 +65,7 @@ use crate::validation::user::validate_user_deletion;
use crate::validation::whitelist::validate_whitelist_token;
use crate::{controller, math};
use crate::{get_then_update_id, QUOTE_SPOT_MARKET_INDEX};
use crate::{load, THIRTEEN_DAY};
use anchor_lang::solana_program::sysvar::instructions;
use anchor_spl::associated_token::AssociatedToken;
use borsh::{BorshDeserialize, BorshSerialize};
Expand Down Expand Up @@ -1873,6 +1873,46 @@ pub fn handle_delete_user(ctx: Context<DeleteUser>) -> Result<()> {
Ok(())
}

pub fn handle_reclaim_rent(ctx: Context<ReclaimRent>) -> Result<()> {
let user_size = ctx.accounts.user.to_account_info().data_len();
let minimum_lamports = ctx.accounts.rent.minimum_balance(user_size);
let current_lamports = ctx.accounts.user.to_account_info().try_lamports()?;
let reclaim_amount = current_lamports.saturating_sub(minimum_lamports);

validate!(
reclaim_amount > 0,
ErrorCode::CantReclaimRent,
"user account has no excess lamports to reclaim"
)?;

**ctx
.accounts
.user
.to_account_info()
.try_borrow_mut_lamports()? = minimum_lamports;

**ctx
.accounts
.authority
.to_account_info()
.try_borrow_mut_lamports()? += reclaim_amount;

let user_stats = &mut load!(ctx.accounts.user_stats)?;

// Skip age check if is no max sub accounts
let max_sub_accounts = ctx.accounts.state.max_number_of_sub_accounts();
let estimated_user_stats_age = user_stats.get_age_ts(Clock::get()?.unix_timestamp);
validate!(
max_sub_accounts == 0 || estimated_user_stats_age >= THIRTEEN_DAY,
ErrorCode::CantReclaimRent,
"user stats too young to reclaim rent. age ={} minimum = {}",
estimated_user_stats_age,
THIRTEEN_DAY
)?;

Ok(())
}

#[access_control(
deposit_not_paused(&ctx.accounts.state)
)]
Expand Down Expand Up @@ -2217,6 +2257,23 @@ pub struct DeleteUser<'info> {
pub authority: Signer<'info>,
}

#[derive(Accounts)]
pub struct ReclaimRent<'info> {
#[account(
mut,
has_one = authority,
)]
pub user: AccountLoader<'info, User>,
#[account(
mut,
has_one = authority
)]
pub user_stats: AccountLoader<'info, UserStats>,
pub state: Box<Account<'info, State>>,
pub authority: Signer<'info>,
pub rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
#[instruction(in_market_index: u16, out_market_index: u16, )]
pub struct Swap<'info> {
Expand Down
4 changes: 4 additions & 0 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ pub mod drift {
handle_delete_user(ctx)
}

pub fn reclaim_rent(ctx: Context<ReclaimRent>) -> Result<()> {
handle_reclaim_rent(ctx)
}

// Keeper Instructions

pub fn fill_perp_order(
Expand Down
3 changes: 1 addition & 2 deletions programs/drift/src/state/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ impl State {
}

pub fn max_number_of_sub_accounts(&self) -> u64 {
#[cfg(test)]
if self.max_number_of_sub_accounts <= 10 {
if self.max_number_of_sub_accounts <= 5 {
return self.max_number_of_sub_accounts as u64;
}

Expand Down
13 changes: 8 additions & 5 deletions programs/drift/src/state/state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ mod get_init_user_fee {

let state = State {
max_initialize_user_fee: 1,
max_number_of_sub_accounts: 100,
number_of_sub_accounts: 8,
max_number_of_sub_accounts: 10,
number_of_sub_accounts: 800,
..State::default()
};

let max_number_of_sub_accounts = state.max_number_of_sub_accounts();
assert_eq!(max_number_of_sub_accounts, 1000);

let init_user_fee = state.get_init_user_fee().unwrap();
assert_eq!(init_user_fee, 0);

let state = State {
max_initialize_user_fee: 1,
max_number_of_sub_accounts: 10,
number_of_sub_accounts: 9,
number_of_sub_accounts: 900,
..State::default()
};

Expand All @@ -30,7 +33,7 @@ mod get_init_user_fee {
let state = State {
max_initialize_user_fee: 1,
max_number_of_sub_accounts: 10,
number_of_sub_accounts: 10,
number_of_sub_accounts: 1000,
..State::default()
};

Expand All @@ -40,7 +43,7 @@ mod get_init_user_fee {
let state = State {
max_initialize_user_fee: 100,
max_number_of_sub_accounts: 10,
number_of_sub_accounts: 10,
number_of_sub_accounts: 1000,
..State::default()
};

Expand Down
45 changes: 39 additions & 6 deletions sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,31 @@ export class DriftClient {
);
}

public async deleteUser(
subAccountId = 0,
txParams?: TxParams
): Promise<TransactionSignature> {
const userAccountPublicKey = getUserAccountPublicKeySync(
this.program.programId,
this.wallet.publicKey,
subAccountId
);

const ix = await this.getUserDeletionIx(userAccountPublicKey);

const { txSig } = await this.sendTransaction(
await this.buildTransaction(ix, txParams),
[],
this.opts
);

const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
await this.users.get(userMapKey)?.unsubscribe();
this.users.delete(userMapKey);

return txSig;
}

public async getUserDeletionIx(userAccountPublicKey: PublicKey) {
const ix = await this.program.instruction.deleteUser({
accounts: {
Expand All @@ -1125,7 +1150,7 @@ export class DriftClient {
return ix;
}

public async deleteUser(
public async reclaimRent(
subAccountId = 0,
txParams?: TxParams
): Promise<TransactionSignature> {
Expand All @@ -1135,21 +1160,29 @@ export class DriftClient {
subAccountId
);

const ix = await this.getUserDeletionIx(userAccountPublicKey);
const ix = await this.getReclaimRentIx(userAccountPublicKey);

const { txSig } = await this.sendTransaction(
await this.buildTransaction(ix, txParams),
[],
this.opts
);

const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
await this.users.get(userMapKey)?.unsubscribe();
this.users.delete(userMapKey);

return txSig;
}

public async getReclaimRentIx(userAccountPublicKey: PublicKey) {
return await this.program.instruction.reclaimRent({
accounts: {
user: userAccountPublicKey,
userStats: this.getUserStatsAccountPublicKey(),
authority: this.wallet.publicKey,
state: await this.getStatePublicKey(),
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
}

public getUser(subAccountId?: number, authority?: PublicKey): User {
subAccountId = subAccountId ?? this.activeSubAccountId;
authority = authority ?? this.authority;
Expand Down
36 changes: 36 additions & 0 deletions sdk/src/idl/drift.json
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,37 @@
],
"args": []
},
{
"name": "reclaimRent",
"accounts": [
{
"name": "user",
"isMut": true,
"isSigner": false
},
{
"name": "userStats",
"isMut": true,
"isSigner": false
},
{
"name": "state",
"isMut": false,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
},
{
"name": "rent",
"isMut": false,
"isSigner": false
}
],
"args": []
},
{
"name": "fillPerpOrder",
"accounts": [
Expand Down Expand Up @@ -11139,6 +11170,11 @@
"code": 6256,
"name": "CantPayUserInitFee",
"msg": "CantPayUserInitFee"
},
{
"code": 6257,
"name": "CantReclaimRent",
"msg": "CantReclaimRent"
}
]
}
3 changes: 3 additions & 0 deletions sdk/src/math/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ export function calculateInitUserFee(stateAccount: StateAccount): BN {
}

export function getMaxNumberOfSubAccounts(stateAccount: StateAccount): BN {
if (stateAccount.maxNumberOfSubAccounts <= 5) {
return new BN(stateAccount.maxNumberOfSubAccounts);
}
return new BN(stateAccount.maxNumberOfSubAccounts).muln(100);
}
24 changes: 20 additions & 4 deletions tests/surgePricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assert } from 'chai';

import { Program } from '@coral-xyz/anchor';

import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';

import {
TestClient,
Expand Down Expand Up @@ -31,7 +31,7 @@ import {
} from '../sdk';
import { calculateInitUserFee } from '../sdk/lib/math/state';

describe('spot deposit and withdraw', () => {
describe('surge pricing', () => {
const provider = anchor.AnchorProvider.local(undefined, {
preflightCommitment: 'confirmed',
skipPreflight: false,
Expand Down Expand Up @@ -93,6 +93,7 @@ describe('spot deposit and withdraw', () => {

after(async () => {
await admin.unsubscribe();
await eventSubscriber.unsubscribe();
});

it('Initialize USDC Market', async () => {
Expand Down Expand Up @@ -172,11 +173,26 @@ describe('spot deposit and withdraw', () => {
const accountInfo = await connection.getAccountInfo(userAccount);
const baseLamports = 31347840;
console.log('expected fee', expectedFee.toNumber());
if (i === 5) {
assert(expectedFee.toNumber() === LAMPORTS_PER_SOL / 100);
if (i === 4) {
// assert(expectedFee.toNumber() === LAMPORTS_PER_SOL / 100);
}
console.log('account info', accountInfo.lamports);
assert(accountInfo.lamports === baseLamports + expectedFee.toNumber());
await sleep(1000);

if (i === 4) {
await admin.updateStateMaxNumberOfSubAccounts(0);
await driftClient.reclaimRent(0);
const accountInfoAfterReclaim = await connection.getAccountInfo(
userAccount
);
console.log(
'account info after reclaim',
accountInfoAfterReclaim.lamports
);
assert(accountInfoAfterReclaim.lamports === baseLamports);
}
await driftClient.unsubscribe();
}
});
});