-
Notifications
You must be signed in to change notification settings - Fork 154
[feature] implement unified order simulation logic #4127
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
base: main
Are you sure you want to change the base?
[feature] implement unified order simulation logic #4127
Conversation
- 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
There was a problem hiding this 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.
crates/shared/src/order_quoting.rs
Outdated
| 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| fee_amount: U256::from(trade_estimate.gas), | |
| fee_amount: fee_parameters.fee(), |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
};There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed.
|
i was blocked on running integration tests for this, so i spent time reading through e2e/tests and discovered i realized i can just run 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 the local e2e test passes. the forked test requires |
There was a problem hiding this 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.
| 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()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed
| 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))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed
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_callwith state overrides to avoid changing blockchain state, and logs failures to Tenderly for debugging.Changes
OrderExecutionSimulatingtrait for pluggable simulationOrderExecutionSimulatorwith settlement encoding and state override preparationOrderQuotervia factory patternFakeOrderExecutionSimulatorto forgive unit testingHow to test
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