Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
48 changes: 23 additions & 25 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,53 @@
# Drift Protocol Dev Container
#

FROM --platform=linux/amd64 rust:1.70.0
FROM --platform=linux/amd64 ubuntu:24.04

ARG DEBIAN_FRONTEND=noninteractive
ARG SOLANA_CLI="1.16.27"
ARG ANCHOR_CLI="0.29.0"
ARG NODE_VERSION="20.18.1"
ARG NODE_VERSION="24.0.0"

ENV HOME="/root"
ENV PATH="/usr/local/cargo/bin:${PATH}"
ENV PATH="/root/.local/share/solana/install/active_release/bin:${PATH}"

RUN mkdir -p /workdir /tmp \
&& apt-get update -qq \
&& apt-get upgrade -qq \
&& apt-get install -y --no-install-recommends \
build-essential git curl wget jq pkg-config python3-pip xz-utils ca-certificates \
libssl-dev libudev-dev bash software-properties-common \
&& add-apt-repository 'deb http://deb.debian.org/debian bookworm main' \
&& apt-get update -qq \
&& apt-get install -y libc6 libc6-dev \
&& rm -rf /var/lib/apt/lists/*
&& apt-get update -qq \
&& apt-get upgrade -qq \
&& apt-get install -y --no-install-recommends \
build-essential git curl wget jq pkg-config python3-pip xz-utils ca-certificates \
libssl-dev libudev-dev bash software-properties-common \
&& rm -rf /var/lib/apt/lists/*

RUN rustup component add rustfmt
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.78.0 \
&& . "$HOME/.cargo/env" \
&& rustup component add rustfmt clippy --toolchain 1.78.0

RUN rustup install 1.78.0 \
&& rustup component add rustfmt clippy --toolchain 1.78.0
ENV PATH="/root/.cargo/bin:${PATH}"

RUN curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz \
&& tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \
&& rm /tmp/node.tar.xz \
&& corepack enable \
&& npm install -g ts-mocha typescript mocha \
&& node -v && npm -v && yarn -v
&& tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \
&& rm /tmp/node.tar.xz \
&& corepack enable \
&& npm install -g ts-mocha typescript mocha \
&& node -v && npm -v && yarn -v

# Solana CLI (x86_64 build)
RUN curl -sSfL "https://github.com/solana-labs/solana/releases/download/v${SOLANA_CLI}/solana-release-x86_64-unknown-linux-gnu.tar.bz2" \
| tar -xjC /tmp \
&& mv /tmp/solana-release/bin/* /usr/local/bin/ \
&& rm -rf /tmp/solana-release
| tar -xjC /tmp \
&& mv /tmp/solana-release/bin/* /usr/local/bin/ \
&& rm -rf /tmp/solana-release

# Anchor CLI
RUN cargo install --git https://github.com/coral-xyz/anchor --tag "v${ANCHOR_CLI}" anchor-cli --locked

# Set up Solana key + config for root
RUN solana-keygen new --no-bip39-passphrase --force \
&& solana config set --url localhost
&& solana config set --url localhost

RUN apt-get update && apt-get install -y zsh curl git \
&& sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& chsh -s /usr/bin/zsh root
&& sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& chsh -s /usr/bin/zsh root

WORKDIR /workdir
2 changes: 2 additions & 0 deletions .mocharc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node-option:
- no-experimental-strip-types
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,6 @@
"chalk-template": "<1.1.1",
"supports-hyperlinks": "<4.1.1",
"has-ansi": "<6.0.1"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
165 changes: 165 additions & 0 deletions programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::optional_accounts::get_token_mint;
use crate::state::amm_cache::{AmmCache, CacheInfo, AMM_POSITIONS_CACHE};
use crate::state::events::{
CurveRecord, DepositDirection, DepositExplanation, DepositRecord, SpotMarketVaultDepositRecord,
TransferFeeAndPnlPoolDirection,
};
use crate::state::fulfillment_params::openbook_v2::{
OpenbookV2Context, OpenbookV2FulfillmentConfig,
Expand Down Expand Up @@ -5245,6 +5246,145 @@ pub fn handle_update_perp_market_config(
Ok(())
}

#[access_control(
perp_market_valid(&ctx.accounts.perp_market_with_fee_pool)
perp_market_valid(&ctx.accounts.perp_market_with_pnl_pool)
)]
pub fn handle_transfer_fee_and_pnl_pool<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, TransferFeeAndPnlPool<'info>>,
amount: u64,
direction: TransferFeeAndPnlPoolDirection,
) -> Result<()> {
let spot_market = &mut load_mut!(ctx.accounts.spot_market)?;

controller::spot_balance::update_spot_market_cumulative_interest(
spot_market,
None,
Clock::get()?.unix_timestamp,
)?;

let same_market = ctx.accounts.perp_market_with_fee_pool.key()
== ctx.accounts.perp_market_with_pnl_pool.key();

if same_market {
let mut perp_market = load_mut!(ctx.accounts.perp_market_with_fee_pool)?;
let fee_pool = &mut perp_market.amm.fee_pool as *mut PoolBalance;
let pnl_pool = &mut perp_market.pnl_pool as *mut PoolBalance;
execute_transfer_between_pools(
amount,
spot_market,
unsafe { &mut *fee_pool },
unsafe { &mut *pnl_pool },
direction,
)?;
perp_market.amm.total_fee_minus_distributions = match direction {
TransferFeeAndPnlPoolDirection::FeeToPnlPool => perp_market
.amm
.total_fee_minus_distributions
.safe_sub(amount.cast()?)?,
TransferFeeAndPnlPoolDirection::PnlToFeePool => perp_market
.amm
.total_fee_minus_distributions
.safe_add(amount.cast()?)?,
};
} else {
let mut perp_market_with_fee_pool = load_mut!(ctx.accounts.perp_market_with_fee_pool)?;
let mut perp_market_with_pnl_pool = load_mut!(ctx.accounts.perp_market_with_pnl_pool)?;
let fee_pool = &mut perp_market_with_fee_pool.amm.fee_pool;
let pnl_pool = &mut perp_market_with_pnl_pool.pnl_pool;
execute_transfer_between_pools(amount, spot_market, fee_pool, pnl_pool, direction)?;
perp_market_with_fee_pool.amm.total_fee_minus_distributions = match direction {
TransferFeeAndPnlPoolDirection::FeeToPnlPool => perp_market_with_fee_pool
.amm
.total_fee_minus_distributions
.safe_sub(amount.cast()?)?,
TransferFeeAndPnlPoolDirection::PnlToFeePool => perp_market_with_fee_pool
.amm
.total_fee_minus_distributions
.safe_add(amount.cast()?)?,
};
}
Ok(())
}

pub fn execute_transfer_between_pools(
amount: u64,
spot_market: &mut SpotMarket,
fee_pool: &mut PoolBalance,
pnl_pool: &mut PoolBalance,
direction: TransferFeeAndPnlPoolDirection,
) -> Result<()> {
let (source_name, dest_name, source_index, dest_index) = match direction {
TransferFeeAndPnlPoolDirection::FeeToPnlPool => {
("fee", "pnl", fee_pool.market_index, pnl_pool.market_index)
}
TransferFeeAndPnlPoolDirection::PnlToFeePool => {
("pnl", "fee", pnl_pool.market_index, fee_pool.market_index)
}
};

msg!(
"transferring {} from perp market {} {} pool -> perp market {} {} pool",
amount,
source_index,
source_name,
dest_index,
dest_name,
);

match direction {
TransferFeeAndPnlPoolDirection::FeeToPnlPool => {
// Op 1: Decrement fee pool
controller::spot_balance::update_spot_balances(
amount.cast::<u128>()?,
&SpotBalanceType::Borrow,
spot_market,
fee_pool,
false,
)?;

// Op 2: Increment pnl pool
controller::spot_balance::update_spot_balances(
amount.cast::<u128>()?,
&SpotBalanceType::Deposit,
spot_market,
pnl_pool,
false,
)?;
}
TransferFeeAndPnlPoolDirection::PnlToFeePool => {
// Op 1: Decrement pnl pool
controller::spot_balance::update_spot_balances(
amount.cast::<u128>()?,
&SpotBalanceType::Borrow,
spot_market,
pnl_pool,
false,
)?;

// Op 2: Increment fee pool
controller::spot_balance::update_spot_balances(
amount.cast::<u128>()?,
&SpotBalanceType::Deposit,
spot_market,
fee_pool,
false,
)?;
}
}

msg!(
"transferred {} fee_pool(market {}) scaled_balance: {} pnl_pool(market {}) scaled_balance: {}",
amount,
fee_pool.market_index,
fee_pool.scaled_balance,
pnl_pool.market_index,
pnl_pool.scaled_balance,
);

Ok(())
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
Expand Down Expand Up @@ -6186,3 +6326,28 @@ pub struct UpdateDelegateUserGovTokenInsuranceStake<'info> {
)]
pub state: Box<Account<'info, State>>,
}

#[derive(Accounts)]
pub struct TransferFeeAndPnlPool<'info> {
#[account(
has_one = admin
)]
pub state: Box<Account<'info, State>>,
pub admin: Signer<'info>,
#[account(mut)]
pub perp_market_with_fee_pool: AccountLoader<'info, PerpMarket>,
#[account(mut)]
pub perp_market_with_pnl_pool: AccountLoader<'info, PerpMarket>,
#[account(
mut,
seeds = [b"spot_market", 0_u16.to_le_bytes().as_ref()],
bump,
)]
pub spot_market: AccountLoader<'info, SpotMarket>,
#[account(
mut,
seeds = [b"spot_market_vault".as_ref(), 0_u16.to_le_bytes().as_ref()],
bump,
)]
pub spot_market_vault: Box<InterfaceAccount<'info, TokenAccount>>,
}
10 changes: 9 additions & 1 deletion programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use math::{bn, constants::*};
use state::oracle::OracleSource;

use crate::controller::position::PositionDirection;
use crate::state::events::TransferFeeAndPnlPoolDirection;
use crate::state::if_rebalance_config::IfRebalanceConfigParams;
use crate::state::oracle::PrelaunchOracleParams;
use crate::state::order_params::{ModifyOrderParams, OrderParams};
Expand All @@ -21,7 +22,6 @@ use crate::state::spot_market::SpotFulfillmentConfigStatus;
use crate::state::state::FeeStructure;
use crate::state::state::*;
use crate::state::user::MarketType;

pub mod controller;
pub mod error;
pub mod ids;
Expand Down Expand Up @@ -2243,6 +2243,14 @@ pub mod drift {
) -> Result<()> {
handle_update_perp_market_config(ctx, market_config)
}

pub fn transfer_fee_and_pnl_pool<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, TransferFeeAndPnlPool<'info>>,
amount: u64,
direction: TransferFeeAndPnlPoolDirection,
) -> Result<()> {
handle_transfer_fee_and_pnl_pool(ctx, amount, direction)
}
}

#[cfg(not(feature = "no-entrypoint"))]
Expand Down
7 changes: 7 additions & 0 deletions programs/drift/src/state/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,3 +919,10 @@ pub struct LPBorrowLendDepositRecord {
impl Size for LPBorrowLendDepositRecord {
const SIZE: usize = 104;
}

#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Eq, Default, Debug)]
pub enum TransferFeeAndPnlPoolDirection {
#[default]
FeeToPnlPool,
PnlToFeePool,
}
48 changes: 48 additions & 0 deletions sdk/src/adminClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
InitializeConstituentParams,
ConstituentStatus,
LPPoolAccount,
TransferFeeAndPnlPoolDirection,
} from './types';
import { DEFAULT_MARKET_NAME, encodeName } from './userName';
import { BN } from '@coral-xyz/anchor';
Expand Down Expand Up @@ -6633,4 +6634,51 @@ export class AdminClient extends DriftClient {
},
});
}

public async transferFeeAndPnlPool(
perpMarketIndexWithFeePool: number,
perpMarketIndexWithPnlPool: number,
amount: BN,
direction: TransferFeeAndPnlPoolDirection
): Promise<TransactionSignature> {
const transferFeeAndPnlPoolIx = await this.getTransferFeeAndPnlPoolIx(
perpMarketIndexWithFeePool,
perpMarketIndexWithPnlPool,
amount,
direction
);
const tx = await this.buildTransaction(transferFeeAndPnlPoolIx);
const { txSig } = await this.sendTransaction(tx, [], this.opts);
return txSig;
}

public async getTransferFeeAndPnlPoolIx(
perpMarketIndexWithFeePool: number,
perpMarketIndexWithPnlPool: number,
amount: BN,
direction: TransferFeeAndPnlPoolDirection
): Promise<TransactionInstruction> {
return await this.program.instruction.transferFeeAndPnlPool(
amount,
direction,
{
accounts: {
admin: this.isSubscribed
? this.getStateAccount().admin
: this.wallet.publicKey,
state: await this.getStatePublicKey(),
perpMarketWithFeePool: await getPerpMarketPublicKey(
this.program.programId,
perpMarketIndexWithFeePool
),
perpMarketWithPnlPool: await getPerpMarketPublicKey(
this.program.programId,
perpMarketIndexWithPnlPool
),
spotMarket: this.getQuoteSpotMarketAccount().pubkey,
spotMarketVault: this.getQuoteSpotMarketAccount().vault,
},
}
);
}
}
Loading
Loading