Skip to content

Partially filled orders support in simulation#4306

Merged
MartinquaXD merged 127 commits intomainfrom
partial-fill-order-simulation
Apr 8, 2026
Merged

Partially filled orders support in simulation#4306
MartinquaXD merged 127 commits intomainfrom
partial-fill-order-simulation

Conversation

@m-sz
Copy link
Copy Markdown
Contributor

@m-sz m-sz commented Apr 1, 2026

Description

The order simulation endpoint so far always tried to fill the order with the original 100% fill amount. This leads to reverts if an order was already partially filled.

Changes

Fetches the order's fill amount using an RPC call at the given block and deducts that from the full order amount. This will cause the simulation to always fill the remaining executable amount.

How to test

E2E test simulating a partially filled order.

m-sz and others added 30 commits March 12, 2026 18:02
Contact and Token addresses now are private structs containing Address.
Implemented Deref for them and updated usages.
Contact and Token addresses now are private structs containing Address.
Implemented Deref for them and updated usages.
Adds dependency on simulator for solver
These can be added as post-processing step at the callsite.

Reverted invalid contracts changes
Addressed comments
Co-authored-by: José Duarte <duarte.gmj@gmail.com>
description: >
Block number to simulate the order at. If not specified, the
simulation uses the latest block.
- in: query
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Spent some time with claude, brushing up on openAPI spec)

The CI is failing because required: false (L819 below) on the blockNumber property inside the SimulationRequest schema isn't valid OpenAPI 3.0.

In Schema Objects, required is only valid as an array on the parent object.

That one should probably fix it 👀

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.

Since you went through all that, you could use the suggest feature in the GitHub to provide a more concrete change

description: >
Block number to simulate the order at. If not specified, the
simulation uses the latest block.
- in: query
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.

Since you went through all that, you could use the suggest feature in the GitHub to provide a more concrete change

Comment on lines +21 to +26
/// Override for how much of the order has already been filled, expressed
/// in the order's fill token (sell token for sell orders, buy token for
/// buy orders). When absent, the current on-chain fill state from the
/// order metadata is used.
#[serde_as(as = "Option<HexOrDecimalU256>")]
pub executed_amount: Option<U256>,
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 don't like this, are we adding all parameters in the query from here on? Feels like a slippery slope to me

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.

This is a GET endpoint, the POST is used to simulate a custom order and has the respective fields. I do not foresee more query parameters than the simulation block number and executed amounts so I would be for keeping it this way.

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.

What's the reason for making the user provide this value in the first place? We could use an RPC call to automatically figure out the remaining fill amount on the given block. (order.amount - settlementContract.filledAmount(orderUid)).

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 will change the implementation to not allow specifying filled amount by the user and issue an RPC call to figure out the executed amount.

&self,
order: &Order,
wrappers: Vec<WrapperCall>,
executed_amount: Option<U256>,
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'm not sure if we should move this up and split the function in two: with and without executed amount — which would eventually be "with and without overrides"

I'd say that for now it can stay but I'm afraid it will gather more parameters if we're not careful

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.

Let's keep it as-is, and reiterate if the number of overrides grows.

// Clearing prices represent the limit price of the order; both order kinds
// produce the same ratio: [buy_amount, sell_amount] for [sell_token,
// buy_token].
let clearing_prices = vec![order.data.buy_amount, order.data.sell_amount];
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.

if they represent the limit prices, why isn't this called "limit_prices" instead, it's much cleaner and there's no confusion with the whole concept of (uniform) clearing prices

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.

It is called clearing prices everywhere where quoting and orders are involved. I would keep it as-is.

Base automatically changed from custom-order-simulation to main April 7, 2026 14:12
Comment on lines 656 to 660
let (_, wrappers) = self
.parse_interactions_and_wrappers(
order.metadata.full_app_data.as_deref().unwrap_or_default(),
)
.map_err(OrderSimulationError::Other)?;
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.

Seems a bit strange to have some of the appdata handled outside of encode_order and some of it inside that function. Is there a reason why it was done 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.

Some of the parts of app data go directly into the "Order" struct, and the wrappers do not. I can change the method to just take the appdata extracted wrappers to keep it in one place.

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.

There is no app data handling inside encode order, the method takes what is provided by the calller (Order struct with filled in interactions) and wrappers that it then encodes.

Comment on lines 768 to 771
"/api/v1/debug/simulation":
post:
operationId: debugSimulationPost
summary: Simulate an arbitrary order.
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.

Is the PR correctly rebased? IIRC there was another PR that introduced simulations for arbitrary orders, right?

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.

It must have been lost during some of the rebases that I have done on the PRs. Let's keep it here then so it is in the openapi spec. Sorry for that.

@MartinquaXD MartinquaXD added this pull request to the merge queue Apr 8, 2026
Merged via the queue into main with commit 7b16a3d Apr 8, 2026
19 checks passed
@MartinquaXD MartinquaXD deleted the partial-fill-order-simulation branch April 8, 2026 07:24
@github-actions github-actions bot locked and limited conversation to collaborators Apr 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants