Skip to content

Commit

Permalink
program: reclaim rent (#763)
Browse files Browse the repository at this point in the history
* init

* add tests

* fix lints

* remove unneeded mut

* remove system program account

* CHANGELOG
  • Loading branch information
crispheaney authored Dec 19, 2023
1 parent 9429c5f commit 8cec21d
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 18 deletions.
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();
}
});
});

0 comments on commit 8cec21d

Please sign in to comment.