Skip to content

Conversation

@theghostmac
Copy link

@theghostmac theghostmac commented Feb 4, 2026

Description

Implement unified order execution simulator infrastructure for end-to-end order validation (#4006).

This PR introduces OrderExecutionSimulator. It allows simulation for order execution, to validate settlement contract state transitions, token transfers and allowances, EIP-1271 signature validity, pre/post-interaction hooks, wrapper and flashloan setup.

I use eth_call with state overrides to avoid changing blockchain state, and logs failures to Tenderly for debugging.

Changes

  • Add OrderExecutionSimulating trait for pluggable simulation
  • Implement OrderExecutionSimulator with settlement encoding and state override preparation
  • Integrate simulator into OrderQuoter via factory pattern
  • Add FakeOrderExecutionSimulator to forgive unit testing
  • Fix service initialization in orderbook and autopilot runs
  • Tenderly logging if the simulation fails.

How to test

just test-e2e quote_verification

Runs the E2E test suite. The simulator is integrated into the verified quote flow so it'll be exercised by any tests that request verified quotes.

Fixes #4006

- Implement settle() call simulation with state overrides
- Support EIP-1271 signature validation
- Track wrapper/flashloan/pre-post-hook execution
- Add balance override support for buy token
@theghostmac theghostmac requested a review from a team as a code owner February 4, 2026 19:58
@theghostmac theghostmac marked this pull request as draft February 4, 2026 19:58
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an order execution simulator. The review has identified several high-severity issues: an incorrect clearing price calculation for same-token trades in the simulation, an incorrect fee amount being used for the simulation order, and a potential panic due to an unwrap() on a Result in the factory.

buy_amount: quoted_buy_amount,
valid_to: u32::MAX, // Simulation doesn't care about time
app_data: AppDataHash::default(),
fee_amount: U256::from(trade_estimate.gas),
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The fee_amount for the simulation order is incorrectly set to U256::from(trade_estimate.gas). The fee amount should be in units of the sell token, not gas units. This will lead to incorrect simulation results. The correct fee amount should be calculated from the fee_parameters which are available in this scope.

Suggested change
fee_amount: U256::from(trade_estimate.gas),
fee_amount: fee_parameters.fee(),

Copy link
Author

Choose a reason for hiding this comment

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

good catch. addressed.

.shared_args
.tenderly
.get_api_instance(&self.components.http_factory, "order_simulation".to_owned())
.unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using .unwrap() on the result of get_api_instance can cause a panic if Tenderly is misconfigured (e.g., invalid API key format), leading to a service crash. It's safer to propagate the error using ? since the order_quoter function returns a Result.

Suggested change
.unwrap()
?

Copy link
Author

Choose a reason for hiding this comment

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

addressed.

Comment on lines 74 to 93
let tokens = if order.data.sell_token < order.data.buy_token {
vec![order.data.sell_token, order.data.buy_token]
} else {
vec![order.data.buy_token, order.data.sell_token]
};

let sell_token_index = tokens
.iter()
.position(|&t| t == order.data.sell_token)
.unwrap();

let buy_token_index = tokens
.iter()
.position(|&t| t == order.data.buy_token)
.unwrap();

// Clearing prices are set such that the order is settled exactly at its limit price.
let mut clearing_prices = vec![U256::ZERO; 2];
clearing_prices[sell_token_index] = order.data.buy_amount;
clearing_prices[buy_token_index] = order.data.sell_amount;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current logic for encoding a settlement does not correctly handle same-token trades (where sell_token == buy_token). When tokens are the same, the tokens vector contains duplicates, and the clearing price calculation overwrites itself, leading to an incorrect price. This will cause simulation failures for same-token trades. The logic should be updated to handle this case by creating a unique list of tokens and setting a 1:1 clearing price for same-token trades.

        let tokens = {
            let mut tokens = vec![order.data.sell_token, order.data.buy_token];
            tokens.sort();
            tokens.dedup();
            tokens
        };

        let sell_token_index = tokens
            .iter()
            .position(|&t| t == order.data.sell_token)
            .unwrap();

        let buy_token_index = tokens
            .iter()
            .position(|&t| t == order.data.buy_token)
            .unwrap();

        // Clearing prices are set such that the order is settled exactly at its limit price.
        // For same-token trades, the price is 1:1.
        let clearing_prices = if tokens.len() == 1 {
            vec![U256::from(1)]
        } else {
            let mut prices = vec![U256::ZERO; tokens.len()];
            prices[sell_token_index] = order.data.buy_amount;
            prices[buy_token_index] = order.data.sell_amount;
            prices
        };

Copy link
Author

Choose a reason for hiding this comment

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

addressed.

@theghostmac
Copy link
Author

theghostmac commented Feb 6, 2026

i was blocked on running integration tests for this, so i spent time reading through e2e/tests and discovered quote_verification.rs.

i realized i can just run just test-e2e quote_verification since it now implicitly uses my OrderExecutionSimulator.

doing so, i ran into signature validation issues - the settlement contract validates signatures, but simulation happens before the order is actually signed. i removed the premature integration into the quote flow and kept the simulator as a standalone component ready for order validation (not quote validation, which verify_quote() already handles).

the local e2e test passes. the forked test requires FORK_URL_MAINNET which isn't set up for me.

FORK_URL_MAINNET must be set to run forked tests: NotPresent

@theghostmac theghostmac marked this pull request as ready for review February 6, 2026 02:02
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an OrderExecutionSimulator for end-to-end order validation. My review focuses on improving the robustness of the service initialization. Specifically, I've identified that a misconfiguration of the optional Tenderly integration could cause the service to panic on startup. I've suggested changes to handle this error gracefully to prevent the service from crashing.

Comment on lines 478 to 483
let tenderly = args
.shared
.tenderly
.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap()
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain.id())));
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using .unwrap() here will cause the service to panic on startup if the Tenderly API is misconfigured. Since Tenderly is used for logging and is not a critical component, it would be more robust to handle the error gracefully, log a warning, and continue without Tenderly simulation. This would prevent a misconfiguration in an optional component from taking down the entire service.

Suggested change
let tenderly = args
.shared
.tenderly
.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap()
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain.id())));
let tenderly = args.shared.tenderly.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap_or_else(|err| {
tracing::warn!(?err, "failed to initialize tenderly api");
None
})
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain.id())));

Copy link
Author

Choose a reason for hiding this comment

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

addressed

Comment on lines 364 to 369
let tenderly = args
.shared
.tenderly
.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap()
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain_id)));
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using .unwrap() here will cause the service to panic on startup if the Tenderly API is misconfigured. Since Tenderly is used for logging and is not a critical component, it would be more robust to handle the error gracefully, log a warning, and continue without Tenderly simulation. This would prevent a misconfiguration in an optional component from taking down the entire service.

Suggested change
let tenderly = args
.shared
.tenderly
.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap()
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain_id)));
let tenderly = args.shared.tenderly.get_api_instance(&http_factory, "order_simulation".to_owned())
.unwrap_or_else(|err| {
tracing::warn!(?err, "failed to initialize tenderly api");
None
})
.map(|t| Arc::new(shared::tenderly_api::TenderlyCodeSimulator::new(t, chain_id)));

Copy link
Author

Choose a reason for hiding this comment

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

addressed

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.

Unified order simulation logic

1 participant