Skip to content

Add instruction to create self order for buffer withdrawals - #153

Merged
fedgiac merged 13 commits into
mainfrom
federico/sc-152-support-placing-orders-from-settlement-program-dedicated-order
Sep 16, 2026
Merged

fedgiac merged 13 commits into
mainfrom
federico/sc-152-support-placing-orders-from-settlement-program-dedicated-order

Conversation

@fedgiac

@fedgiac fedgiac commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Add the to place a self order: an order owned by the settlement state PDA that sells tokens sitting in a buffer (or, strictly speaking, any token controlled by the state PDA). This is gated by the withdrawal authority introduced in #150 (which has been renamed to self-order authority after a reviewer's request).

How a self order is placed

CreateSelfOrder basically copies CreateOrder except for the initial owner check. In particular, it takes a full order intent as part of the input bytes. The main difference is that the order must be owned by the state PDA and the withdraw authority (not the owner) must sign the transaction.
The owner check in the intent is extremely important: without it, the withdraw authority could steal funds from any user who delegated the settlement PDA.

Why is the PR so big?

Not that much is happening in the actual contract. I tried to isolate unrelated changes in #152, but we didn't gain much, most of the code is still new. A good part of this is the IDL-related changes. The tests are also relatively large.

How to test

CI should be enough, new tests have been added. Also for the IDL.

@fedgiac
fedgiac requested a review from a team as a code owner September 15, 2026 19:34
@linear-code

linear-code Bot commented Sep 15, 2026

Copy link
Copy Markdown

SC-152

@kaze-cow kaze-cow left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would argue that any of the changes for the programs/settlement/idl/client should be dropped from this PR and possibly moved to a new PR. While we do already have support for the close-to-identical function CreateOrder explicitly within the generated JS library, no integrator is going to be using the CreateWithrawalOrder and I am pretty sure the value of having this extra code is pretty low.

I would consider renaming create_withdrawal_order to create_self_order since it is not technically required that the order need be a withdrawal. Technically the order could simply be for some internal accounting purpose in the future instead, so the naming doesn't really explain fundamentally what is happening.

Arguably unlike CreateOrder, it would be useful to be able to create many orders at once with this instruction (ex. we basically know that in a single fee withdrawal cycle there could be tens of different tokens that need to be converted into SOL). This would probably be pretty simple too, as order_pda account could simply by repeated and intent bytes could be a multiplier of EncodedOrderIntent length. Inside the processor it would be a basic for loop.

Comment on lines +14 to +39
/// Allocates a per-order PDA (see [`crate::pda::order`]) for an order owned by
/// the settlement state PDA, so the fees that accumulate in the buffer accounts
/// can be sold through a regular settlement. The PDA's storage layout is the
/// same as any other order's, [`crate::data::order::EncodedOrderAccount`].
///
/// Unlike [`CreateOrder`](crate::instruction::create_order::CreateOrder), the
/// order isn't authenticated by its owner's signature: the owner is the
/// settlement state PDA, which has no key. Instead the instruction is gated by
/// the [`WithdrawalAuthority`](crate::Role::WithdrawalAuthority), a privileged
/// account that signs it. The program forces `intent.owner` to be the state
/// PDA, so a withdrawal order can only sell funds stored in the buffers.
/// Otherwise, the order this function created is a normal order and settles
/// through the standard `BeginSettle`/`FinalizeSettle` flow.
///
/// The withdrawal authority is trusted to choose sound order parameters.
/// The only enforced parameters are `created_on_chain` (should be true) and
/// the owner (should be the state PDA).
///
/// `payer` funds the new order PDA's rent and is recorded as its `created_by`
/// address, so `ReclaimOrder` refunds the rent there.
///
/// Wire format: `[discriminator=10, ..intent bytes]`,
/// `1 + EncodedOrderIntent::SIZE` bytes. Required accounts:
/// `[authority (S), payer (W,S), state_pda (R), order_pda (W),
/// system_program (R)]`. The system program needs to be available but doesn't
/// need to sit at that specific position, unlike the others.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we simplify this giant description down to just "Same as CreateOrder, but only the withdraw_authority can call and the owner is set to state_pda. The purpose is to allow direct management of the settlement program's buffer funds." or so. Looking at the parameters (and processor) they are the same so its easiest to understand this way

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Simplified a bit: 11b7f36
But I'd still like to keep it a bit verbose for consistency with the other instructions. I agree it says a lot for a code comment, but as documentation for the package I think it's helpful to describe the instruction in detail. (In general I always wondered whether the more verbose comments should be moved to the interface instead of having them in the client.)

Comment thread interface/src/instruction/create_withdrawal_order.rs Outdated
Comment on lines +70 to +76
pub struct CreateWithdrawalOrderInput<'a, A> {
pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
pub authority: &'a A,
pub payer: &'a A,
pub state_pda: &'a A,
pub order_pda: &'a A,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the fields between CreateOrderInput and CreateWithdrawalOrderInput are so similar that you could hypothetically use it here as a nested struct and then include state_pda (the only "added" parameter). This would even allow validating off of the same facilities already provided in the create_order.rs instruction parser.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried it but I don't really like the result: ddc6e91
I'd say it's nicer to keep everything flat for consistency. Also, I dislike that decoding requires you to do owner: state_pda,, meaning that the caller needs to be aware that the variable has another real name.

Comment thread programs/settlement/tests/common/withdrawal.rs Outdated
Base automatically changed from federico/sc-152-support-placing-orders-from-settlement-program-refactor to main September 16, 2026 08:42
@fedgiac

fedgiac commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

no integrator is going to be using the CreateWithrawalOrder and I am pretty sure the value of having this extra code is pretty low.

I don't particularly care about having CreateWithrawalOrder in the JS library, so if it's easy to drop it I'm fine with it. But I do mind about having it available but broken, which is why I think something should be done about this.

I would argue that any of the changes for the programs/settlement/idl/client should be dropped from this PR and possibly moved to a new PR.

I'm fine with this, on the other hand the extra IDL code is about 80 lines so I'm not sure it's worth splitting it off. Will still do it if requested again. (The IDL itself needs to be changed if we want CI to pass, this is why we don't save that many diff lines.)

renaming create_withdrawal_order to `create_self_order

70555b3

create many orders at once with this instruction

I didn't mention it but this is specifically out of scope. It would require rethinking the instruction and I don't think it's that easy. This PR is already >1k lines.
I created an issue for this: #154

@fedgiac
fedgiac requested a review from kaze-cow September 16, 2026 11:28
@fedgiac fedgiac changed the title Add instruction to create buffer withdrawal order Add instruction to create self order for buffer withdrawals Sep 16, 2026

@kaze-cow kaze-cow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

very minor comments remain

Comment thread interface/src/instruction/create_self_order.rs Outdated
Comment thread programs/settlement/tests/common/order.rs
Comment thread programs/settlement/tests/create_self_order.rs
Comment thread programs/settlement/tests/settle_self_order.rs
Comment thread DESIGN.md Outdated
@fedgiac
fedgiac merged commit ce05684 into main Sep 16, 2026
18 checks passed
@fedgiac
fedgiac deleted the federico/sc-152-support-placing-orders-from-settlement-program-dedicated-order branch September 16, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants