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: forced delete user init #1341

Merged
merged 12 commits into from
Nov 21, 2024
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: force delete user init ([#1341](https://github.com/drift-labs/protocol-v2/pull/1341))
- program: rm withdraw fee ([#1334](https://github.com/drift-labs/protocol-v2/pull/1334))

### Fixes
Expand Down
239 changes: 231 additions & 8 deletions programs/drift/src/instructions/keeper.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
use std::cell::RefMut;
use std::convert::TryFrom;

use anchor_lang::prelude::*;
use anchor_spl::associated_token::get_associated_token_address;
use anchor_spl::token::spl_token;
use anchor_spl::token_2022::spl_token_2022;
use anchor_spl::token_interface::{TokenAccount, TokenInterface};
use solana_program::instruction::Instruction;
use solana_program::sysvar::instructions::{
load_current_index_checked, load_instruction_at_checked, ID as IX_ID,
};

use crate::controller::insurance::update_user_stats_if_stake_amount;
use crate::controller::orders::cancel_orders;
use crate::controller::position::PositionDirection;
use crate::controller::spot_balance::update_spot_balances;
use crate::controller::token::{receive, send_from_program_vault};
use crate::error::ErrorCode;
use crate::ids::swift_server;
use crate::ids::{admin_hot_wallet, swift_server};
use crate::instructions::constraints::*;
use crate::instructions::optional_accounts::{load_maps, AccountMaps};
use crate::math::casting::Cast;
Expand All @@ -19,8 +26,9 @@ use crate::math::margin::{calculate_user_equity, meets_settle_pnl_maintenance_ma
use crate::math::orders::{estimate_price_from_side, find_bids_and_asks_from_users};
use crate::math::safe_math::SafeMath;
use crate::math::spot_withdraw::validate_spot_market_vault_amount;
use crate::math_error;
use crate::optional_accounts::{get_token_mint, update_prelaunch_oracle};
use crate::state::events::SwiftOrderRecord;
use crate::state::events::{DeleteUserRecord, OrderActionExplanation, SwiftOrderRecord};
use crate::state::fill_mode::FillMode;
use crate::state::fulfillment_params::drift::MatchFulfillmentParams;
use crate::state::fulfillment_params::openbook_v2::OpenbookV2FulfillmentParams;
Expand All @@ -35,12 +43,12 @@ use crate::state::order_params::{
use crate::state::paused_operations::PerpOperation;
use crate::state::perp_market::{ContractType, MarketStatus, PerpMarket};
use crate::state::perp_market_map::{
get_market_set_for_user_positions, get_market_set_from_list, get_writable_perp_market_set,
get_writable_perp_market_set_from_vec, MarketSet, PerpMarketMap,
get_market_set_for_spot_positions, get_market_set_for_user_positions, get_market_set_from_list,
get_writable_perp_market_set, get_writable_perp_market_set_from_vec, MarketSet, PerpMarketMap,
};
use crate::state::settle_pnl_mode::SettlePnlMode;
use crate::state::spot_fulfillment_params::SpotFulfillmentParams;
use crate::state::spot_market::SpotMarket;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::spot_market_map::{
get_writable_spot_market_set, get_writable_spot_market_set_from_many, SpotMarketMap,
};
Expand All @@ -53,10 +61,10 @@ use crate::state::user::{
};
use crate::state::user_map::{load_user_map, load_user_maps, UserMap, UserStatsMap};
use crate::validation::sig_verification::{extract_ed25519_ix_signature, verify_ed25519_digest};
use crate::validation::user::validate_user_is_idle;
use crate::validation::user::{validate_user_deletion, validate_user_is_idle};
use crate::{
controller, digest_struct, load, math, print_error, OracleSource, GOV_SPOT_MARKET_INDEX,
MARGIN_PRECISION,
controller, digest_struct, load, math, print_error, safe_decrement, OracleSource,
GOV_SPOT_MARKET_INDEX, MARGIN_PRECISION,
};
use crate::{load_mut, QUOTE_PRECISION_U64};
use crate::{validate, QUOTE_PRECISION_I128};
Expand Down Expand Up @@ -2124,6 +2132,194 @@ pub fn handle_disable_user_high_leverage_mode<'c: 'info, 'info>(
Ok(())
}

pub fn handle_force_delete_user<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, ForceDeleteUser<'info>>,
) -> Result<()> {
let state = &ctx.accounts.state;

let keeper_key = *ctx.accounts.keeper.key;

let user_key = ctx.accounts.user.key();
let user = &mut load_mut!(ctx.accounts.user)?;
let user_stats = &mut load_mut!(ctx.accounts.user_stats)?;

let slot = Clock::get()?.slot;
let now = Clock::get()?.unix_timestamp;
let AccountMaps {
perp_market_map,
spot_market_map,
mut oracle_map,
} = load_maps(
&mut ctx.remaining_accounts.iter().peekable(),
&MarketSet::new(),
&get_market_set_for_spot_positions(&user.spot_positions),
slot,
Some(state.oracle_guard_rails),
)?;

// check the user equity

let (user_equity, _) =
calculate_user_equity(user, &perp_market_map, &spot_market_map, &mut oracle_map)?;

let max_equity = QUOTE_PRECISION_I128 / 20;
validate!(
user_equity <= max_equity,
ErrorCode::DefaultError,
"user equity must be less than {}",
max_equity
)?;

#[cfg(not(feature = "anchor-test"))]
{
let slots_since_last_active = slot.safe_sub(user.last_active_slot)?;

validate!(
slots_since_last_active >= 18144000, // 60 * 60 * 24 * 7 * 4 * 3 / .4 (~3 months)
ErrorCode::DefaultError,
"user not inactive for long enough: {}",
slots_since_last_active
)?;
}

// cancel all open orders
let canceled_order_ids = cancel_orders(
user,
&user_key,
Some(&keeper_key),
&perp_market_map,
&spot_market_map,
&mut oracle_map,
now,
slot,
OrderActionExplanation::None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add explanation?

None,
None,
None,
)?;

for spot_position in user.spot_positions.iter_mut() {
if spot_position.is_available() {
continue;
}

let spot_market = &mut spot_market_map.get_ref_mut(&spot_position.market_index)?;
let oracle_price_data = oracle_map.get_price_data(&spot_market.oracle)?;

controller::spot_balance::update_spot_market_cumulative_interest(
spot_market,
Some(oracle_price_data),
now,
)?;

let token_amount = spot_position.get_token_amount(spot_market)?;
let balance_type = spot_position.balance_type;

let token_program_pubkey = if spot_market.token_program == 1 {
spl_token_2022::ID
} else {
spl_token::ID
};
let token_program = &ctx
.remaining_accounts
.iter()
.find(|acc| acc.key() == token_program_pubkey)
.map(|acc| Interface::try_from(acc))
.unwrap()
.unwrap();

let spot_market_mint = &spot_market.mint;
let mint_account_info = ctx
.remaining_accounts
.iter()
.find(|acc| acc.key() == spot_market_mint.key())
.map(|acc| InterfaceAccount::try_from(acc).unwrap());

let keeper_vault = get_associated_token_address(&keeper_key, spot_market_mint);
let keeper_vault_account_info = ctx
.remaining_accounts
.iter()
.find(|acc| acc.key() == keeper_vault.key())
.map(|acc| InterfaceAccount::try_from(acc))
.unwrap()
.unwrap();

let spot_market_vault = spot_market.vault;
let mut spot_market_vault_account_info = ctx
.remaining_accounts
.iter()
.find(|acc| acc.key() == spot_market_vault.key())
.map(|acc| InterfaceAccount::try_from(acc))
.unwrap()
.unwrap();

if balance_type == SpotBalanceType::Deposit {
update_spot_balances(
token_amount,
&SpotBalanceType::Borrow,
spot_market,
spot_position,
true,
)?;

send_from_program_vault(
&token_program,
&spot_market_vault_account_info,
&keeper_vault_account_info,
&ctx.accounts.drift_signer,
state.signer_nonce,
token_amount.cast()?,
&mint_account_info,
)?;
} else {
update_spot_balances(
token_amount,
&SpotBalanceType::Deposit,
spot_market,
spot_position,
false,
)?;

receive(
token_program,
&keeper_vault_account_info,
&spot_market_vault_account_info,
&ctx.accounts.keeper.to_account_info(),
token_amount.cast()?,
&mint_account_info,
)?;
}

spot_market_vault_account_info.reload()?;
math::spot_withdraw::validate_spot_market_vault_amount(
spot_market,
spot_market_vault_account_info.amount,
)?;
}

validate_user_deletion(
user,
user_stats,
&ctx.accounts.state,
Clock::get()?.unix_timestamp,
)?;

safe_decrement!(user_stats.number_of_sub_accounts, 1);

let state = &mut ctx.accounts.state;
safe_decrement!(state.number_of_sub_accounts, 1);

emit!(DeleteUserRecord {
ts: now,
user_authority: *ctx.accounts.authority.key,
user: user_key,
sub_account_id: user.sub_account_id,
keeper: Some(*ctx.accounts.keeper.key),
});

Ok(())
}

#[derive(Accounts)]
pub struct FillOrder<'info> {
pub state: Box<Account<'info, State>>,
Expand Down Expand Up @@ -2588,3 +2784,30 @@ pub struct DisableUserHighLeverageMode<'info> {
#[account(mut)]
pub high_leverage_mode_config: AccountLoader<'info, HighLeverageModeConfig>,
}

#[derive(Accounts)]
pub struct ForceDeleteUser<'info> {
#[account(
mut,
has_one = authority,
close = authority
)]
pub user: AccountLoader<'info, User>,
#[account(
mut,
has_one = authority
)]
pub user_stats: AccountLoader<'info, UserStats>,
#[account(mut)]
pub state: Box<Account<'info, State>>,
/// CHECK: authority
#[account(mut)]
pub authority: AccountInfo<'info>,
#[account(
mut,
constraint = keeper.key() == admin_hot_wallet::id()
)]
pub keeper: Signer<'info>,
/// CHECK: forced drift_signer
pub drift_signer: AccountInfo<'info>,
}
6 changes: 6 additions & 0 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ pub mod drift {
handle_delete_user(ctx)
}

pub fn force_delete_user<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, ForceDeleteUser<'info>>,
) -> Result<()> {
handle_force_delete_user(ctx)
}

pub fn delete_swift_user_orders<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, DeleteSwiftUserOrders>,
) -> Result<()> {
Expand Down
10 changes: 10 additions & 0 deletions programs/drift/src/state/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,16 @@ pub struct SpotMarketVaultDepositRecord {
pub amount: u64,
}

#[event]
pub struct DeleteUserRecord {
/// unix_timestamp of action
pub ts: i64,
pub user_authority: Pubkey,
pub user: Pubkey,
pub sub_account_id: u16,
pub keeper: Option<Pubkey>,
}

pub fn emit_stack<T: AnchorSerialize + Discriminator, const N: usize>(event: T) -> DriftResult {
let mut data_buf = [0u8; N];
let mut out_buf = [0u8; N];
Expand Down
16 changes: 15 additions & 1 deletion programs/drift/src/state/perp_market_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use crate::state::traits::Size;
use solana_program::msg;
use std::panic::Location;

use super::user::SpotPosition;

pub struct PerpMarketMap<'a>(pub BTreeMap<u16, AccountLoader<'a, PerpMarket>>);

impl<'a> PerpMarketMap<'a> {
Expand Down Expand Up @@ -247,7 +249,19 @@ pub fn get_market_set_from_list(market_indexes: [u16; 5]) -> MarketSet {
pub fn get_market_set_for_user_positions(user_positions: &PerpPositions) -> MarketSet {
let mut writable_markets = MarketSet::new();
for position in user_positions.iter() {
writable_markets.insert(position.market_index);
if !position.is_available() {
writable_markets.insert(position.market_index);
}
}
writable_markets
}

pub fn get_market_set_for_spot_positions(spot_positions: &[SpotPosition]) -> MarketSet {
let mut writable_markets = MarketSet::new();
for position in spot_positions.iter() {
if !position.is_available() {
writable_markets.insert(position.market_index);
}
}
writable_markets
}
Loading
Loading