Simulate EIP-1271 orders at creation#4366
Draft
squadgazzz wants to merge 63 commits intonew-api-simulator-cratefrom
Draft
Simulate EIP-1271 orders at creation#4366squadgazzz wants to merge 63 commits intonew-api-simulator-cratefrom
squadgazzz wants to merge 63 commits intonew-api-simulator-cratefrom
Conversation
Runs the OrderSimulator concurrently with the cheap isValidSignature check. In shadow mode (default), logs disagreements via metrics and structured logs but returns the cheap check's result. In enforce mode, (cheap Pass, sim Fail) is upgraded to ValidationError::SimulationFailed; other combinations stay unchanged. Infra errors never reject. Covers scope from plan Tasks 3, 4 and 5: shadow-mode quadrants, enforce-mode cases, infra/skip-flag/no-sim paths.
The Shadow/Enforce distinction is a mode variant, not a property of the capability — the same simulator infrastructure is active in both modes. Keeping "shadow" only where it names a mode variant, and renaming the trait, error, mode enum, metrics, constant, config fields, and module accordingly.
Doc comments and local variable names still described the capability as 'shadow' — rewritten to reference the mode variant only where it genuinely applies (config docs, test names that exercise Shadow mode, the Shadow enum variant).
The simulator, mode, and timeout are only meaningful together. Collapsing them into a single Option<Eip1271SimConfig> field lets the call site in run.rs return None cleanly when order_simulation isn't configured, instead of passing placeholder mode/timeout values that aren't read.
… to Simulator Matches the project's convention of -ing suffix for traits (OrderValidating/OrderValidator, SignatureValidating, BalanceFetching). The former Eip1271SimConfig bundle becomes the concrete Eip1271Simulator struct, and the trait it depends on becomes Eip1271Simulating.
The helper now accepts the full simulator bundle instead of three separate (simulator, mode, timeout) args. Introduces shadow_mode_sim / enforce_mode_sim helpers to keep test call sites tight.
The constant is only used by tests; keeping it at module scope (and public) implied an API contract with the configs crate that doesn't exist. configs::orderbook::default_eip1271_sim_timeout is authoritative at runtime.
The existing EIP-1271 check is an isValidSignature call; 'cheap' was editorializing on cost rather than describing what it does. Renaming to 'signature' for the metric axis, outcome enum, and supporting names. Also collapsing sim_only_total into total by adding a 'skipped' value to the signature axis — one counter with a signature × sim matrix covers every case the two used to cover.
…configs, logs The Eip1271Simulator struct keeps its -Simulator suffix (matching OrderValidator/-Validating), but everywhere sim was used as a modifier or qualifier (enum variants, error type, metric subsystem/labels, config fields, log messages, test names) it now reads as simulation.
Operators can now opt out of the simulation at order creation on a per-chain basis without giving up the /debug/simulation endpoint. The shared mode enum stays binary (Shadow/Enforce); Disabled translates to None for OrderValidator at the wiring layer. The OrderSimulator is still constructed for the debug endpoint. Also removed the redundant impls_trait compile-check test in eip1271_simulation.rs — the impl block above already enforces that at compile time.
Mock both the signature validator and the simulator with times(0) and submit an Eip712 (EOA) order. Catches a regression where the sim is accidentally wired to run for non-1271 orders.
The seven near-identical tests covering every (signature, simulation, mode) combination collapsed into one table-driven test with a single mock-driven helper. Failure messages include a label per row so any regression still pinpoints the failing cell. Also inlined the now-unused enforce_mode_simulator helper and replaced shadow_mode_simulator with a general simulator_with_mode.
- Move simulation_time histogram timer inside simulation_fut so it no longer captures the max of sig-check + simulation latency. - Warn-level (not info) for simulation failures in the eip1271_skip_creation_validation path, matching the normal path. - Drop Default derive on the shared Eip1271SimulationMode since the operational default lives in configs (Disabled) and no code reaches for it. Updated the doc to clarify the split. - New configs test asserting "shadow" deserializes to the Shadow variant.
f960037 to
5940c11
Compare
5940c11 to
b162fb4
Compare
…ototype-eip1271-on-new-api
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Same as #4355: orderbook accepts an EIP-1271 order if the signer contract's
isValidSignaturesays yes. That single check is enough to let through Aave flashloan-style orders where the signature passes but the post-hook can never settle. We catch these later, after they're in an auction, wasting solver cycles.A symmetric class (Euler-style) is too strict on signatures but would actually settle fine. Those are rejected today.
Fix
This PR is the successor to #4355, rebuilt on top of
new-api-simulator-crate(@MartinquaXD'sSettlementSimulatorrefactor). At order-creation time, we run the signature check and a full-order simulation concurrently, then combine the results.Per chain, a mode config picks what to do with the combined result:
disabled(default): same as today. Signature check only.shadow: both run, disagreements land in metrics and logs, behaviour unchanged.enforce: if signature passes but simulation fails, reject with HTTP 400Eip1271SimulationFailed.Infra errors (RPC, timeout, Tenderly) never reject in any mode. Signature-fail cases return the same
InvalidEip1271Signatureas before, regardless of the simulation result.What changed vs #4355
SettlementSimulator::new_simulation_builder()instead of the legacyOrderSimulator.parameters_from_app_datahandles pre/post hooks, custom wrappers, and Aave flashloan configs uniformly from the user-signed app_data. The earlier prototype injected these manually.WrapperConfig::Flashloan(...)) routes through the deployedFlashLoanRouter, so the validation-time simulation now reflects the real Aave flashloan flow end-to-end. The borrower-to-trader transfer concern raised in the Simulate EIP-1271 orders at creation #4355 review is handled by the user-signed pre-hook (deploy helper, fund from factory), whichparameters_from_app_dataincludes automatically.orderbook::runpasses theFlashLoanRouterdeployment address toSettlementSimulator::newinstead ofAddress::ZERO, which would otherwise silently no-op the wrapper call.Rollout
Same as #4355. Land as
disabled. Flip toshadowin prod to see the matrix on real traffic. Flip toenforceonce we trust the sim.Changes
shared::order_validation: newEip1271Simulatingtrait,Eip1271Simulatorbundle (simulator + mode + timeout) threaded intoOrderValidator. Validation runs signature + sim concurrently viatokio::join!with a per-call timeout, emits aneip1271_simulation_total{signature, simulation}Prometheus matrix, upgrades enforce-mode disagreements toValidationError::SimulationFailed.orderbook::eip1271_simulation:OrderSimulatorAdapterdrivingSettlementSimulator::new_simulation_builder()+parameters_from_app_data.orderbook::run: wire the deployedFlashLoanRouteraddress.configs:Eip1271SimulationModeenum withdisabled,shadow,enforcevariants.How to test
Unit tests in
shared::order_validationcover the signature × simulation matrix, enforce-mode rejection, fail-open on infra errors, theeip1271_skip_creation_validationpath, and the no-simulator-configured path.configstests deserialize the three modes.Local-node e2e in
crates/e2e/tests/e2e/eip1271_creation_simulation.rs:app_data.protocol.wrapperspoints at a custom always-revert wrapper. WithEip1271SimulationMode::Enforce, the orderbook returns HTTP 400Eip1271SimulationFailed.Forked-mainnet replay in
crates/simulator/tests/aave_replay.rs(gated onMAINNET_RPC_URL, otherwise skipped).A full e2e test against a forked node isn't viable:
OrderValidator::partial_validatechecksvalid_toagainstSystemTime::now(), and any historical order'svalid_tois in the past on a fresh test run, so the orderbook rejects withInsufficientValidTobefore the simulation ever runs. These tests therefore bypass the validator and driveSettlementSimulatordirectly.flashloan.amountrewritten past Aave's WETH liquidity. Asserts the simulation reverts withexecution reverted. The eth_call goes toFlashLoanRouter, which forwards toAaveBorrower, which asks the Aave Pool for the loan. Aave is what reverts (insufficient liquidity), but the revert only reaches us because the router and borrower addresses are correctly wired. If the wrapper had no-op'd againstAddress::ZEROthe simulation would have succeeded silently.