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

new place_and_take_perp_order flag #1211

Closed
crispheaney opened this issue Aug 30, 2024 · 11 comments
Closed

new place_and_take_perp_order flag #1211

crispheaney opened this issue Aug 30, 2024 · 11 comments
Labels
help wanted Extra attention is needed

Comments

@crispheaney
Copy link
Member

Currently place_and_take_perp_order tx will succeed whether or not there is a fill.

I suggest retro'ing the unused _maker_order_id into a flag that specifies when the tx should fail:

  1. there is no fill
  2. there is only a partial fill

With these new flags, it will be easier for devs to write client code and may lead to less tx actually needed to land onchain (you can skip sending the tx if it fails sim).

@crispheaney crispheaney added the help wanted Extra attention is needed label Aug 30, 2024
@Gsuz
Copy link

Gsuz commented Aug 31, 2024

yes please!

@cxp-13
Copy link

cxp-13 commented Sep 4, 2024

May I ask if the fulfill_perp_order_ with amm function means to fill everything? Do I need to rewrite a function to implement this feature?

@cxp-13
Copy link

cxp-13 commented Sep 4, 2024

One question I have is assuming that in the case of a partial order that can be fulfilled, the program is in the stage of filling the order and the data of the order and position has actually changed. If I subsequently cancel this order. Will the data that has been changed before be restored? In other words is this whole program atomic?

@cxp-13
Copy link

cxp-13 commented Sep 9, 2024

When I tried to implement this new functionality in the handle_place_and_take_perp_order function, my thought process was to run a simulation of the fill_perp_order function first to get the number of underlying assets that were expected to be completed. But I ran into a problem where running the real fill_perp_order function after a successful simulation run reported a stack overflow.

[2024-09-09T07:15:19.932634619Z DEBUG solana_runtime::message_processor::stable_log] Program log: Error: memory allocation failed, out of memory
[2024-09-09T07:15:19.932660769Z DEBUG solana_runtime::message_processor::stable_log] Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 492157 of 599850 compute units
[2024-09-09T07:15:19.932694232Z DEBUG solana_runtime::message_processor::stable_log] Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH failed: SBF program panicked

@cxp-13
Copy link

cxp-13 commented Sep 9, 2024

Here is the code I tried. Feel free to discuss.

#[access_control(
    fill_not_paused(&ctx.accounts.state)
)]
pub fn handle_place_and_take_perp_order<'c: 'info, 'info>(
    ctx: Context<'_, '_, 'c, 'info, PlaceAndTake<'info>>,
    params: OrderParams,
    should_fail_on_partial_or_no_fill: Option<bool>,
) -> Result<()> {
    let clock = Clock::get()?;
    let state = &ctx.accounts.state;

    let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable();
    let AccountMaps {
        perp_market_map,
        spot_market_map,
        mut oracle_map,
    } = load_maps(
        remaining_accounts_iter,
        &get_writable_perp_market_set(params.market_index),
        &MarketSet::new(),
        clock.slot,
        Some(state.oracle_guard_rails),
    )?;

    if params.post_only != PostOnlyParam::None {
        msg!("post_only cant be used in place_and_take");
        return Err(print_error!(ErrorCode::InvalidOrderPostOnly)().into());
    }

    let (makers_and_referrer, makers_and_referrer_stats) =
        load_user_maps(remaining_accounts_iter, true)?;

    let is_immediate_or_cancel = params.immediate_or_cancel;

    controller::repeg::update_amm(
        params.market_index,
        &perp_market_map,
        &mut oracle_map,
        &ctx.accounts.state,
        &Clock::get()?,
    )?;

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

    controller::orders::place_perp_order(
        &ctx.accounts.state,
        &mut user,
        user_key,
        &perp_market_map,
        &spot_market_map,
        &mut oracle_map,
        &Clock::get()?,
        params,
        PlaceOrderOptions::default(),
    )?;
    
    drop(user);

    let user = &mut ctx.accounts.user;
    let order_id = load!(user)?.get_last_order_id();
    // let user_clone = &user.clone();

    msg!("=== handle_place_and_take_perp_order === clone test");

    // Simulation test of the function fill_perp_order with input core structure parameters all cloned
    let(test_base_asset_amount, test_quote_asset_amount) = controller::orders::fill_perp_order(
        order_id,
        &ctx.accounts.state.clone(),
        &user.clone(),
        &ctx.accounts.user_stats.clone(),
        &spot_market_map.clone(),
        &perp_market_map.clone(),
        &mut oracle_map.clone(),
        &user.clone(),
        &ctx.accounts.user_stats.clone(),
        &makers_and_referrer.clone(),
        &makers_and_referrer_stats.clone(),
        None,
        &Clock::get()?,
        FillMode::PlaceAndTake,
    )?;


    msg!("test base_asset_amount: {}", test_base_asset_amount);
    msg!("test quote_asset_amount: {}", test_quote_asset_amount);

    let(base_asset_amount, quote_asset_amount) = controller::orders::fill_perp_order(
        order_id,
        &ctx.accounts.state,
        user,
        &ctx.accounts.user_stats,
        &spot_market_map,
        &perp_market_map,
        &mut oracle_map,
        &user.clone(),
        &ctx.accounts.user_stats.clone(),
        &makers_and_referrer,
        &makers_and_referrer_stats,
        None,
        &Clock::get()?,
        FillMode::PlaceAndTake,
        &maker_orders_info,
    )?;

    msg!("real_base_asset_amount: {}", base_asset_amount);
    msg!("real_asset_amount: {}", quote_asset_amount);


    let order_exists = load!(ctx.accounts.user)?
        .orders
        .iter()
        .any(|order| order.order_id == order_id);

    if is_immediate_or_cancel && order_exists {
        controller::orders::cancel_order_by_order_id(
            order_id,
            &ctx.accounts.user,
            &perp_market_map,
            &spot_market_map,
            &mut oracle_map,
            &Clock::get()?,
        )?;
    }

    Ok(())
}

@wphan
Copy link
Member

wphan commented Sep 9, 2024

i dont think you need to run fill_perp_order twice, the base_asset_amount returned is the amount filled, so if base_asset_amount != 0, then a fill (full or partial) has happened.

You probably also want to use an enum instead of bool to allow for the ix to allow user to choose when to revert (partial fill, no fill, or dont revert)

@wphan
Copy link
Member

wphan commented Sep 9, 2024

One question I have is assuming that in the case of a partial order that can be fulfilled, the program is in the stage of filling the order and the data of the order and position has actually changed. If I subsequently cancel this order. Will the data that has been changed before be restored? In other words is this whole program atomic?

If there is a partial fill and the order is canceled, the user state does not revert if the transaction is successful (aka. immediate-or-cancel). This feature will allow the transaction to error in the event of partial or no fill - which will result in no change to user account (also known as fill-or-kill)

@cxp-13
Copy link

cxp-13 commented Sep 9, 2024

i dont think you need to run fill_perp_order twice, the base_asset_amount returned is the amount filled, so if base_asset_amount != 0, then a fill (full or partial) has happened.我认为您不需要运行 fill_perp_order 两次,返回的 base_asset_amount 是填充的数量,因此如果 base_asset_amount != 0,则发生填充(全部或部分)。

You probably also want to use an enum instead of bool to allow for the ix to allow user to choose when to revert (partial fill, no fill, or dont revert)您可能还想使用枚举而不是布尔值来允许 ix 允许用户选择何时恢复(部分填充、不填充或不恢复)

I'll say what I understand, because I looked at the code, and I found that it cancels the order if it is partially filled successfully, but the successful part is not withdrawn, or returned to the state it was in before it all started. So, my understanding is to simulate walking through the process first, and if it doesn't fill completely it doesn't perform the later steps.

@cxp-13
Copy link

cxp-13 commented Sep 9, 2024

One question I have is assuming that in the case of a partial order that can be fulfilled, the program is in the stage of filling the order and the data of the order and position has actually changed. If I subsequently cancel this order. Will the data that has been changed before be restored? In other words is this whole program atomic?

If there is a partial fill and the order is canceled, the user state does not revert if the transaction is successful (aka. immediate-or-cancel). This feature will allow the transaction to error in the event of partial or no fill - which will result in no change to user account (also known as fill-or-kill)

So, what do you think are the steps to correctly implement this feature?

@crispheaney
Copy link
Member Author

crispheaney commented Sep 10, 2024

You don't need to simulate because if you throw an error in the tx, the whole state is reverted. So you can just check if there is a full or partial fill after the fill perp order is called

@crispheaney
Copy link
Member Author

prototyped this here: https://github.com/drift-labs/protocol-v2/pull/1218/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants