Skip to content

program: programmatic rebalance between protocol owned if holdings #1653

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
120 changes: 119 additions & 1 deletion programs/drift/src/controller/insurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ use crate::math::insurance::{
calculate_if_shares_lost, calculate_rebase_info, if_shares_to_vault_amount,
vault_amount_to_if_shares,
};
use crate::math::orders::{calculate_fill_price};
use crate::math::safe_math::SafeMath;
use crate::math::spot_balance::get_token_amount;
use crate::math::spot_withdraw::validate_spot_market_vault_amount;
use crate::state::events::{InsuranceFundRecord, InsuranceFundStakeRecord, StakeAction};
use crate::state::events::{InsuranceFundRecord, InsuranceFundStakeRecord, StakeAction, InsuranceFundSwapRecord};
use crate::state::insurance_fund_stake::InsuranceFundStake;
use crate::state::perp_market::PerpMarket;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::state::State;
use crate::state::user::UserStats;
use crate::{emit, validate, FUEL_START_TS, GOV_SPOT_MARKET_INDEX, QUOTE_SPOT_MARKET_INDEX};
use crate::state::if_rebalance_config::IfRebalanceConfig;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -948,3 +950,119 @@ pub fn resolve_perp_pnl_deficit(

insurance_withdraw.cast()
}

pub fn handle_if_begin_swap(
if_rebalance_config: &mut IfRebalanceConfig,
in_insurance_fund_vault_amount: u64,
out_insurance_fund_vault_amount: u64,
in_spot_market: &mut SpotMarket,
out_spot_market: &mut SpotMarket,
in_amount: u64,
now: i64,
) -> DriftResult<()> {
if now > if_rebalance_config.epoch_start_ts.safe_add(if_rebalance_config.epoch_duration)? {
if_rebalance_config.epoch_start_ts = now;
if_rebalance_config.epoch_in_amount = 0;
}

apply_rebase_to_insurance_fund(in_insurance_fund_vault_amount, in_spot_market)?;
apply_rebase_to_insurance_fund(out_insurance_fund_vault_amount, out_spot_market)?;


Ok(())
}

pub fn handle_if_end_swap(
if_rebalance_config: &mut IfRebalanceConfig,
in_insurance_fund_vault_amount_after: u64,
out_insurance_fund_vault_amount_after: u64,
in_spot_market: &mut SpotMarket,
out_spot_market: &mut SpotMarket,
in_amount: u64,
out_amount: u64,
out_oracle_price: u64,
now: i64,
) -> DriftResult<()> {
let in_insurance_fund_vault_amount_before = in_insurance_fund_vault_amount_after.safe_add(in_amount)?;
let out_insurance_fund_vault_amount_before = out_insurance_fund_vault_amount_after.safe_sub(out_amount)?;

let in_if_total_shares_before = in_spot_market.insurance_fund.total_shares;
let out_if_total_shares_before = out_spot_market.insurance_fund.total_shares;
let in_if_user_shares_before = in_spot_market.insurance_fund.user_shares;
let out_if_user_shares_before = out_spot_market.insurance_fund.user_shares;

let in_shares = vault_amount_to_if_shares(in_amount, in_spot_market.insurance_fund.total_shares, in_insurance_fund_vault_amount_before)?;
let out_shares = vault_amount_to_if_shares(out_amount, out_spot_market.insurance_fund.total_shares, out_insurance_fund_vault_amount_before)?;

// validate shares less than protocol shares
validate!(
in_shares < in_spot_market.insurance_fund.get_protocol_shares()?,
ErrorCode::InsufficientIFShares,
"in_shares={} < total_shares={}",
in_shares,
in_spot_market.insurance_fund.get_protocol_shares()?
)?;

// increment spot market insurance funds total shares
in_spot_market.insurance_fund.total_shares = in_spot_market.insurance_fund.total_shares.safe_sub(in_shares)?;
out_spot_market.insurance_fund.total_shares = out_spot_market.insurance_fund.total_shares.safe_add(out_shares)?;

// increment config current in amount
if_rebalance_config.current_in_amount = if_rebalance_config.current_in_amount.safe_add(in_amount)?;
if_rebalance_config.epoch_in_amount = if_rebalance_config.epoch_in_amount.safe_add(in_amount)?;
// increment config current out amount
if_rebalance_config.current_out_amount = if_rebalance_config.current_out_amount.safe_add(out_amount)?;

validate!(
if_rebalance_config.epoch_in_amount <= if_rebalance_config.epoch_max_in_amount,
ErrorCode::InvalidIfRebalanceSwap,
"epoch_in_amount={} > epoch_max_in_amount={}",
if_rebalance_config.epoch_in_amount, if_rebalance_config.epoch_max_in_amount
)?;

let oracle_twap = out_spot_market.historical_oracle_data.last_oracle_price_twap;

validate!(
out_oracle_price <= oracle_twap.cast::<u64>()?,
ErrorCode::InvalidIfRebalanceSwap,
"out_oracle_price={} > oracle_twap={}",
out_oracle_price, oracle_twap
)?;

let swap_price = calculate_fill_price(in_amount, out_amount, out_spot_market.get_precision())?;

let max_slippage_bps = if_rebalance_config.max_slippage_bps.cast::<u64>()?;
let max_slippage = out_oracle_price / (10000 / max_slippage_bps.max(1));

validate!(
swap_price <= out_oracle_price.safe_add(max_slippage)?,
ErrorCode::InvalidIfRebalanceSwap,
"swap_price={} > out_oracle_price={} + max_slippage={}",
swap_price, out_oracle_price, max_slippage
)?;

emit!(InsuranceFundSwapRecord {
ts: now,
rebalance_config: if_rebalance_config.pubkey,
in_market_index: if_rebalance_config.in_market_index,
out_market_index: if_rebalance_config.out_market_index,
in_amount,
out_amount,
out_oracle_price,
out_oracle_price_twap: oracle_twap,
in_vault_amount_before: in_insurance_fund_vault_amount_before,
out_vault_amount_before: out_insurance_fund_vault_amount_before,
in_fund_vault_amount_after: in_insurance_fund_vault_amount_after,
out_fund_vault_amount_after: out_insurance_fund_vault_amount_after,
in_if_total_shares_before,
out_if_total_shares_before,
in_if_user_shares_before,
out_if_user_shares_before,
in_if_total_shares_after: in_spot_market.insurance_fund.total_shares,
out_if_total_shares_after: out_spot_market.insurance_fund.total_shares,
in_if_user_shares_after: in_spot_market.insurance_fund.user_shares,
out_if_user_shares_after: out_spot_market.insurance_fund.user_shares,
});

Ok(())
}
4 changes: 4 additions & 0 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,10 @@ pub enum ErrorCode {
InvalidSignedMsgUserOrdersResize,
#[msg("Could not deserialize high leverage mode config")]
CouldNotDeserializeHighLeverageModeConfig,
#[msg("Invalid If Rebalance Config")]
InvalidIfRebalanceConfig,
#[msg("Invalid If Rebalance Swap")]
InvalidIfRebalanceSwap,
}

#[macro_export]
Expand Down
77 changes: 77 additions & 0 deletions programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use crate::state::perp_market::{
ContractTier, ContractType, InsuranceClaim, MarketStatus, PerpMarket, PoolBalance, AMM,
};
use crate::state::perp_market_map::{get_writable_perp_market_set, MarketSet};
use crate::state::if_rebalance_config::{IfRebalanceConfig, IfRebalanceConfigParams};
use crate::state::protected_maker_mode_config::ProtectedMakerModeConfig;
use crate::state::pyth_lazer_oracle::{PythLazerOracle, PYTH_LAZER_ORACLE_SEED};
use crate::state::spot_market::{
Expand Down Expand Up @@ -4595,6 +4596,49 @@ pub fn handle_admin_deposit<'c: 'info, 'info>(
Ok(())
}

pub fn handle_initialize_if_rebalance_config(
ctx: Context<InitializeIfRebalanceConfig>,
params: IfRebalanceConfigParams,
) -> Result<()> {
let clock = Clock::get()?;
let now = clock.unix_timestamp;

let pubkey = ctx.accounts.if_rebalance_config.to_account_info().key;
let mut config = ctx.accounts.if_rebalance_config.load_init()?;

config.pubkey = *pubkey;
config.name = params.name;
config.total_in_amount = params.total_in_amount;
config.current_in_amount = 0;
config.epoch_max_in_amount = params.epoch_max_in_amount;
config.epoch_duration = params.epoch_duration;
config.out_market_index = params.out_market_index;
config.in_market_index = params.in_market_index;
config.max_slippage_bps = params.max_slippage_bps;
config.swap_mode = params.swap_mode;
config.status = 0;

config.validate()?;

Ok(())
}

pub fn handle_update_if_rebalance_config(
ctx: Context<UpdateIfRebalanceConfig>,
params: IfRebalanceConfigParams,
) -> Result<()> {
let mut config = load_mut!(ctx.accounts.if_rebalance_config)?;

config.total_in_amount = params.total_in_amount;
config.epoch_max_in_amount = params.epoch_max_in_amount;
config.epoch_duration = params.epoch_duration;
config.max_slippage_bps = params.max_slippage_bps;

config.validate()?;

Ok(())
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
Expand Down Expand Up @@ -5368,3 +5412,36 @@ pub struct AdminDeposit<'info> {
pub admin_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
pub token_program: Interface<'info, TokenInterface>,
}

#[derive(Accounts)]
#[instruction(params: IfRebalanceConfigParams)]
pub struct InitializeIfRebalanceConfig<'info> {
#[account(mut)]
pub admin: Signer<'info>,
#[account(
init,
seeds = [b"if_rebalance_config".as_ref(), params.in_market_index.to_le_bytes().as_ref(), params.out_market_index.to_le_bytes().as_ref()],
space = IfRebalanceConfig::SIZE,
bump,
payer = admin
)]
pub if_rebalance_config: AccountLoader<'info, IfRebalanceConfig>,
#[account(
has_one = admin
)]
pub state: Box<Account<'info, State>>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateIfRebalanceConfig<'info> {
#[account(mut)]
pub admin: Signer<'info>,
#[account(mut)]
pub if_rebalance_config: AccountLoader<'info, IfRebalanceConfig>,
#[account(
has_one = admin
)]
pub state: Box<Account<'info, State>>,
}
Loading
Loading