From 6897fecfd3ffcfed916febaed6d0db5c949ecf08 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 17:30:19 +0100 Subject: [PATCH 001/154] Add EIP-1271 shadow simulator scaffolding --- crates/shared/src/order_validation.rs | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 21bf3a781c..d0c81fe94d 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -47,6 +47,52 @@ use { tracing::instrument, }; +/// Outcome of the shadow simulation against a created order. +#[derive(Debug)] +pub enum ShadowSimError { + /// The simulation ran and the transaction reverted. `reason` is the + /// revert string returned by the EVM (or a Tenderly reason string). + Reverted { + reason: String, + tenderly_url: Option, + }, + /// The simulation could not run (RPC failure, Tenderly error, malformed + /// input, timeout). Treated as fail-open in both shadow and enforce + /// modes. + Infra(anyhow::Error), +} + +/// Optional hook used by `OrderValidator` to run a full order simulation +/// next to the cheap EIP-1271 signature check. A concrete implementation +/// lives in `crates/orderbook/src/eip1271_shadow_sim.rs`. +/// +/// The trait exists in `shared` because `OrderValidator` lives in `shared` +/// and cannot depend upward on `orderbook`. It is designed to be replaced +/// by a direct `simulator::OrderSimulator` dependency once the simulator +/// crate is refactored. +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] +#[async_trait::async_trait] +pub trait Eip1271ShadowSimulator: Send + Sync { + async fn simulate(&self, order: &Order) -> Result<(), ShadowSimError>; +} + +/// Mode controlling whether the shadow sim can reject orders. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Eip1271ShadowSimMode { + /// Log disagreements, emit metrics. Never reject. **Default.** + Shadow, + /// If the cheap check passes but the shadow sim fails, reject the + /// order with `ValidationError::SimulationFailed`. Infra errors still + /// never reject (fail-open). + Enforce, +} + +impl Default for Eip1271ShadowSimMode { + fn default() -> Self { + Self::Shadow + } +} + #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -152,6 +198,12 @@ pub enum ValidationError { /// An invalid EIP-1271 signature, where the on-chain validation check /// reverted or did not return the expected value. InvalidEip1271Signature(B256), + /// The shadow simulation returned a revert in enforce mode. Only + /// possible when the cheap 1271 signature check passed but the full + /// order simulation failed. + SimulationFailed { + reason: String, + }, ZeroAmount, IncompatibleSigningScheme, TooManyLimitOrders, From 4d636882d3f649d76f1df26c3f4b5be4b2a18a24 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 17:39:27 +0100 Subject: [PATCH 002/154] Fix clippy and align SimulationFailed with tuple convention --- crates/shared/src/order_validation.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index d0c81fe94d..d740e7695a 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -77,9 +77,10 @@ pub trait Eip1271ShadowSimulator: Send + Sync { } /// Mode controlling whether the shadow sim can reject orders. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub enum Eip1271ShadowSimMode { /// Log disagreements, emit metrics. Never reject. **Default.** + #[default] Shadow, /// If the cheap check passes but the shadow sim fails, reject the /// order with `ValidationError::SimulationFailed`. Infra errors still @@ -87,12 +88,6 @@ pub enum Eip1271ShadowSimMode { Enforce, } -impl Default for Eip1271ShadowSimMode { - fn default() -> Self { - Self::Shadow - } -} - #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -200,10 +195,9 @@ pub enum ValidationError { InvalidEip1271Signature(B256), /// The shadow simulation returned a revert in enforce mode. Only /// possible when the cheap 1271 signature check passed but the full - /// order simulation failed. - SimulationFailed { - reason: String, - }, + /// order simulation failed. The Tenderly URL, when available, is + /// logged separately and is intentionally not surfaced here. + SimulationFailed(String), ZeroAmount, IncompatibleSigningScheme, TooManyLimitOrders, From 7eb008b99b8b35a96c9dc158ca1daad6d81e0a2a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 17:54:30 +0100 Subject: [PATCH 003/154] Thread optional EIP-1271 shadow simulator into OrderValidator --- crates/orderbook/src/api/post_order.rs | 3 ++ crates/orderbook/src/run.rs | 5 ++- crates/shared/src/order_validation.rs | 54 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index 9db253913b..d3de53f7e5 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -188,6 +188,9 @@ impl IntoResponse for ValidationErrorWrapper { ), ) .into_response(), + ValidationError::SimulationFailed(reason) => { + (StatusCode::BAD_REQUEST, error("SimulationFailed", reason)).into_response() + } ValidationError::InsufficientBalance => ( StatusCode::BAD_REQUEST, error( diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 912680642c..217a17fadf 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -41,7 +41,7 @@ use { }, shared::{ order_quoting::{self, OrderQuoter}, - order_validation::{OrderValidPeriodConfiguration, OrderValidator}, + order_validation::{Eip1271ShadowSimMode, OrderValidPeriodConfiguration, OrderValidator}, }, simulator::swap_simulator::SwapSimulator, std::{future::Future, net::SocketAddr, sync::Arc, time::Duration}, @@ -389,6 +389,9 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index d740e7695a..e664471fb9 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -294,6 +294,12 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, + #[allow(dead_code)] + shadow_simulator: Option>, + #[allow(dead_code)] + shadow_sim_mode: Eip1271ShadowSimMode, + #[allow(dead_code)] + shadow_sim_timeout: Duration, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -364,6 +370,9 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, + shadow_simulator: Option>, + shadow_sim_mode: Eip1271ShadowSimMode, + shadow_sim_timeout: Duration, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -381,6 +390,9 @@ impl OrderValidator { quoter, balance_fetcher, signature_validator, + shadow_simulator, + shadow_sim_mode, + shadow_sim_timeout, limit_order_counter, max_limit_orders_per_user, code_fetcher, @@ -1146,6 +1158,9 @@ mod tests { Arc::new(MockOrderQuoting::new()), Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1291,6 +1306,9 @@ mod tests { Arc::new(MockOrderQuoting::new()), Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1372,6 +1390,9 @@ mod tests { Arc::new(MockOrderQuoting::new()), Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1457,6 +1478,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), signature_validating, + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), max_limit_orders_per_user, Arc::new(MockCodeFetching::new()), @@ -1670,6 +1694,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), signature_validating, + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -1743,6 +1770,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), signature_validating, + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -1804,6 +1834,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1858,6 +1891,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1916,6 +1952,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1977,6 +2016,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2037,6 +2079,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validator), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2104,6 +2149,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2195,6 +2243,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2607,6 +2658,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validating), + None, + Eip1271ShadowSimMode::Shadow, + Duration::from_secs(2), Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), From 3d074e4baaaad0063a3862057c153a66d4b11a0f Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:14:40 +0100 Subject: [PATCH 004/154] Extract shadow sim timeout constant and rename HTTP error code --- crates/orderbook/src/api/post_order.rs | 8 ++++--- crates/orderbook/src/run.rs | 9 ++++++-- crates/shared/src/order_validation.rs | 32 +++++++++++++++----------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index d3de53f7e5..5965bfd4e0 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -188,9 +188,11 @@ impl IntoResponse for ValidationErrorWrapper { ), ) .into_response(), - ValidationError::SimulationFailed(reason) => { - (StatusCode::BAD_REQUEST, error("SimulationFailed", reason)).into_response() - } + ValidationError::SimulationFailed(reason) => ( + StatusCode::BAD_REQUEST, + error("Eip1271SimulationFailed", reason), + ) + .into_response(), ValidationError::InsufficientBalance => ( StatusCode::BAD_REQUEST, error( diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 217a17fadf..889b5cc3cb 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -41,7 +41,12 @@ use { }, shared::{ order_quoting::{self, OrderQuoter}, - order_validation::{Eip1271ShadowSimMode, OrderValidPeriodConfiguration, OrderValidator}, + order_validation::{ + DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271ShadowSimMode, + OrderValidPeriodConfiguration, + OrderValidator, + }, }, simulator::swap_simulator::SwapSimulator, std::{future::Future, net::SocketAddr, sync::Arc, time::Duration}, @@ -391,7 +396,7 @@ pub async fn run(config: Configuration) { signature_validator, None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index e664471fb9..0159b3683c 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -88,6 +88,10 @@ pub enum Eip1271ShadowSimMode { Enforce, } +/// Default per-call timeout for the EIP-1271 shadow simulation. Mirrored +/// by `configs::orderbook::default_shadow_sim_timeout` in Task 6. +pub const DEFAULT_SHADOW_SIM_TIMEOUT: Duration = Duration::from_secs(2); + #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -1160,7 +1164,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1308,7 +1312,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1392,7 +1396,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1480,7 +1484,7 @@ mod tests { signature_validating, None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), max_limit_orders_per_user, Arc::new(MockCodeFetching::new()), @@ -1696,7 +1700,7 @@ mod tests { signature_validating, None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -1772,7 +1776,7 @@ mod tests { signature_validating, None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -1836,7 +1840,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1893,7 +1897,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1954,7 +1958,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2018,7 +2022,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2081,7 +2085,7 @@ mod tests { Arc::new(signature_validator), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2151,7 +2155,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2245,7 +2249,7 @@ mod tests { Arc::new(MockSignatureValidating::new()), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2660,7 +2664,7 @@ mod tests { Arc::new(signature_validating), None, Eip1271ShadowSimMode::Shadow, - Duration::from_secs(2), + DEFAULT_SHADOW_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), From d68cb905bdf8c69bc9eb553ded1737beeff15b76 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:24:20 +0100 Subject: [PATCH 005/154] Integrate EIP-1271 shadow simulation in OrderValidator 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. --- crates/shared/src/order_validation.rs | 664 ++++++++++++++++++++++++-- 1 file changed, 633 insertions(+), 31 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 0159b3683c..ae481ebc95 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -29,6 +29,7 @@ use { OrderData, OrderKind, OrderMetadata, + OrderUid, SellTokenSource, VerificationError, }, @@ -92,6 +93,143 @@ pub enum Eip1271ShadowSimMode { /// by `configs::orderbook::default_shadow_sim_timeout` in Task 6. pub const DEFAULT_SHADOW_SIM_TIMEOUT: Duration = Duration::from_secs(2); +#[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] +#[metric(subsystem = "eip1271_shadow_sim")] +struct ShadowSimMetrics { + /// Shadow sim outcome vs. the cheap check. Labels are each one of + /// `pass | fail | infra`, giving a 3x3 confusion matrix. + #[metric(labels("cheap", "sim"))] + total: prometheus::IntCounterVec, + /// Shadow sim outcome when the cheap check was skipped via + /// `eip1271_skip_creation_validation`. Label is `pass | fail | infra`. + #[metric(labels("sim"))] + sim_only_total: prometheus::IntCounterVec, + /// Duration of the shadow simulation. + duration_seconds: prometheus::Histogram, +} + +impl ShadowSimMetrics { + fn get() -> &'static Self { + Self::instance(observe::metrics::get_storage_registry()) + .expect("unexpected error getting ShadowSimMetrics instance") + } +} + +#[derive(Copy, Clone, Debug)] +enum CheapOutcome { + Pass, + Fail, + Infra, +} + +#[derive(Debug)] +enum SimOutcome { + Pass, + Fail { + reason: String, + tenderly_url: Option, + }, + Infra(anyhow::Error), +} + +impl CheapOutcome { + fn label(&self) -> &'static str { + match self { + Self::Pass => "pass", + Self::Fail => "fail", + Self::Infra => "infra", + } + } +} + +impl SimOutcome { + fn label(&self) -> &'static str { + match self { + Self::Pass => "pass", + Self::Fail { .. } => "fail", + Self::Infra(_) => "infra", + } + } +} + +fn classify_cheap(res: &Result) -> CheapOutcome { + match res { + Ok(_) => CheapOutcome::Pass, + Err(SignatureValidationError::Invalid) => CheapOutcome::Fail, + Err(SignatureValidationError::Other(_)) => CheapOutcome::Infra, + } +} + +fn classify_sim(res: &Result<(), ShadowSimError>) -> SimOutcome { + match res { + Ok(()) => SimOutcome::Pass, + Err(ShadowSimError::Reverted { + reason, + tenderly_url, + }) => SimOutcome::Fail { + reason: reason.clone(), + tenderly_url: tenderly_url.clone(), + }, + Err(ShadowSimError::Infra(err)) => SimOutcome::Infra(anyhow!("{err}")), + } +} + +fn record_shadow_outcome( + cheap: CheapOutcome, + sim: &SimOutcome, + order_uid: OrderUid, + owner: Address, +) { + ShadowSimMetrics::get() + .total + .with_label_values(&[cheap.label(), sim.label()]) + .inc(); + + let disagreement = matches!( + (&cheap, sim), + (CheapOutcome::Pass, SimOutcome::Fail { .. }) | (CheapOutcome::Fail, SimOutcome::Pass) + ); + if disagreement { + let (reason, tenderly_url) = match sim { + SimOutcome::Fail { + reason, + tenderly_url, + } => (reason.as_str(), tenderly_url.as_deref()), + _ => ("", None), + }; + tracing::warn!( + %order_uid, + %owner, + cheap = cheap.label(), + sim = sim.label(), + reason, + ?tenderly_url, + "eip1271 shadow-sim disagreement", + ); + } else if let SimOutcome::Infra(err) = sim { + tracing::warn!(%order_uid, %owner, err = %err, "eip1271 shadow-sim infra error"); + } +} + +fn build_preview_order_for_sim( + data: &OrderData, + interactions: &Interactions, + owner: Address, + uid: OrderUid, + signature: Signature, +) -> Order { + Order { + metadata: OrderMetadata { + owner, + uid, + ..Default::default() + }, + data: *data, + signature, + interactions: interactions.clone(), + } +} + #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -298,11 +436,8 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - #[allow(dead_code)] shadow_simulator: Option>, - #[allow(dead_code)] shadow_sim_mode: Eip1271ShadowSimMode, - #[allow(dead_code)] shadow_sim_timeout: Duration, limit_order_counter: Arc, max_limit_orders_per_user: u64, @@ -406,6 +541,105 @@ impl OrderValidator { } } + async fn run_eip1271_checks( + &self, + check: SignatureCheck, + preview_order: &Order, + hash: B256, + ) -> Result { + if self.eip1271_skip_creation_validation { + if let Some(sim) = &self.shadow_simulator { + self.run_shadow_sim_only(sim.as_ref(), preview_order).await; + } + return Ok(0u64); + } + + let cheap_fut = self + .signature_validator + .validate_signature_and_get_additional_gas(check); + + let Some(sim) = &self.shadow_simulator else { + return cheap_fut.await.map_err(|err| match err { + SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), + SignatureValidationError::Other(err) => ValidationError::Other(err), + }); + }; + + let sim = sim.clone(); + let sim_timeout = self.shadow_sim_timeout; + let order = preview_order.clone(); + let sim_fut = async move { + tokio::time::timeout(sim_timeout, sim.simulate(&order)) + .await + .unwrap_or_else(|_| Err(ShadowSimError::Infra(anyhow!("shadow sim timeout")))) + }; + + let timer = ShadowSimMetrics::get().duration_seconds.start_timer(); + let (cheap_res, sim_res) = tokio::join!(cheap_fut, sim_fut); + drop(timer); + + let cheap_outcome = classify_cheap(&cheap_res); + let sim_outcome = classify_sim(&sim_res); + record_shadow_outcome( + cheap_outcome, + &sim_outcome, + preview_order.metadata.uid, + preview_order.metadata.owner, + ); + + match (cheap_res, &sim_outcome, self.shadow_sim_mode) { + (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271ShadowSimMode::Enforce) => { + Err(ValidationError::SimulationFailed(reason.clone())) + } + (Ok(gas), _, _) => Ok(gas), + (Err(SignatureValidationError::Invalid), _, _) => { + Err(ValidationError::InvalidEip1271Signature(hash)) + } + (Err(SignatureValidationError::Other(err)), _, _) => Err(ValidationError::Other(err)), + } + } + + async fn run_shadow_sim_only(&self, sim: &dyn Eip1271ShadowSimulator, preview_order: &Order) { + let timer = ShadowSimMetrics::get().duration_seconds.start_timer(); + let res = tokio::time::timeout(self.shadow_sim_timeout, sim.simulate(preview_order)).await; + drop(timer); + let outcome = match res { + Ok(Ok(())) => SimOutcome::Pass, + Ok(Err(ShadowSimError::Reverted { + reason, + tenderly_url, + })) => SimOutcome::Fail { + reason, + tenderly_url, + }, + Ok(Err(ShadowSimError::Infra(err))) => SimOutcome::Infra(err), + Err(_) => SimOutcome::Infra(anyhow!("shadow sim timeout")), + }; + ShadowSimMetrics::get() + .sim_only_total + .with_label_values(&[outcome.label()]) + .inc(); + match &outcome { + SimOutcome::Fail { + reason, + tenderly_url, + } => tracing::info!( + order_uid = %preview_order.metadata.uid, + owner = %preview_order.metadata.owner, + reason = %reason, + ?tenderly_url, + "eip1271 shadow-sim (cheap check skipped)", + ), + SimOutcome::Infra(err) => tracing::warn!( + order_uid = %preview_order.metadata.uid, + owner = %preview_order.metadata.owner, + err = %err, + "eip1271 shadow-sim infra error (cheap check skipped)", + ), + SimOutcome::Pass => {} + } + } + async fn check_max_limit_orders(&self, owner: Address) -> Result<(), ValidationError> { let num_limit_orders = self .limit_order_counter @@ -702,34 +936,28 @@ impl OrderValidating for OrderValidator { let uid = data.uid(domain_separator, owner); let verification_gas_limit = if let Signature::Eip1271(signature) = &order.signature { - if self.eip1271_skip_creation_validation { - tracing::debug!(?signature, "skipping EIP-1271 signature validation"); - // We don't care! Because we are skipping validation anyway - 0u64 - } else { - let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); - self.signature_validator - .validate_signature_and_get_additional_gas(SignatureCheck { - signer: owner, - hash: hash.0, - signature: signature.to_owned(), - interactions: app_data.interactions.pre.clone(), - balance_override: app_data.inner.protocol.flashloan.as_ref().map(|loan| { - BalanceOverrideRequest { - token: loan.token, - holder: loan.receiver, - amount: loan.amount, - } - }), - }) - .await - .map_err(|err| match err { - SignatureValidationError::Invalid => { - ValidationError::InvalidEip1271Signature(hash) - } - SignatureValidationError::Other(err) => ValidationError::Other(err), - })? - } + let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); + let check = SignatureCheck { + signer: owner, + hash: hash.0, + signature: signature.to_owned(), + interactions: app_data.interactions.pre.clone(), + balance_override: app_data.inner.protocol.flashloan.as_ref().map(|loan| { + BalanceOverrideRequest { + token: loan.token, + holder: loan.receiver, + amount: loan.amount, + } + }), + }; + let preview_order = build_preview_order_for_sim( + &data, + &app_data.interactions, + owner, + uid, + order.signature.clone(), + ); + self.run_eip1271_checks(check, &preview_order, hash).await? } else { // in any other case, just apply 0 0u64 @@ -2701,4 +2929,378 @@ mod tests { assert_eq!(quote_id, returned_quote_id.and_then(|quote| quote.id)); } + + fn make_1271_order_creation() -> OrderCreation { + OrderCreation { + valid_to: time::now_in_epoch_seconds() + 2, + sell_token: Address::with_last_byte(1), + buy_token: Address::with_last_byte(2), + buy_amount: alloy::primitives::U256::from(1), + sell_amount: alloy::primitives::U256::from(1), + fee_amount: alloy::primitives::U256::ZERO, + from: Some(Address::repeat_byte(1)), + signature: Signature::Eip1271(vec![1, 2, 3]), + app_data: OrderCreationAppData::Full { + full: "{}".to_string(), + }, + ..Default::default() + } + } + + fn build_1271_validator( + signature_validator: MockSignatureValidating, + shadow_simulator: Option>, + shadow_sim_mode: Eip1271ShadowSimMode, + shadow_sim_timeout: Duration, + eip1271_skip_creation_validation: bool, + ) -> OrderValidator { + let mut order_quoter = MockOrderQuoting::new(); + order_quoter + .expect_find_quote() + .returning(|_, _| Ok(Default::default())); + let mut balance_fetcher = MockBalanceFetching::new(); + balance_fetcher + .expect_can_transfer() + .returning(|_, _| Ok(())); + let mut limit_order_counter = MockLimitOrderCounting::new(); + limit_order_counter.expect_count().returning(|_| Ok(0u64)); + let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); + OrderValidator::new( + native_token, + Arc::new(order_validation::banned::Users::none()), + OrderValidPeriodConfiguration::any(), + eip1271_skip_creation_validation, + Default::default(), + HooksTrampoline::Instance::new( + Address::from([0xcf; 20]), + ProviderBuilder::new() + .connect_mocked_client(Asserter::new()) + .erased(), + ), + Arc::new(order_quoter), + Arc::new(balance_fetcher), + Arc::new(signature_validator), + shadow_simulator, + shadow_sim_mode, + shadow_sim_timeout, + Arc::new(limit_order_counter), + 0, + Arc::new(MockCodeFetching::new()), + Default::default(), + u64::MAX, + SameTokensPolicy::Disallow, + ) + } + + #[tokio::test] + async fn shadow_sim_pass_cheap_pass_accepts_in_shadow_mode() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| Ok(())); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!(result.is_ok(), "expected Ok, got {result:?}"); + } + + #[tokio::test] + async fn shadow_sim_fail_cheap_pass_accepts_in_shadow_mode() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| { + Err(ShadowSimError::Reverted { + reason: "hook revert".into(), + tenderly_url: None, + }) + }); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!(result.is_ok(), "expected Ok in shadow mode, got {result:?}"); + } + + #[tokio::test] + async fn shadow_sim_pass_cheap_fail_rejects_with_invalid_sig_in_shadow_mode() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Err(SignatureValidationError::Invalid)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| Ok(())); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let err = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await + .unwrap_err(); + assert!( + matches!(err, ValidationError::InvalidEip1271Signature(_)), + "got {err:?}" + ); + } + + #[tokio::test] + async fn shadow_sim_fail_cheap_fail_rejects_with_invalid_sig_in_shadow_mode() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Err(SignatureValidationError::Invalid)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| { + Err(ShadowSimError::Reverted { + reason: "x".into(), + tenderly_url: None, + }) + }); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let err = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await + .unwrap_err(); + assert!( + matches!(err, ValidationError::InvalidEip1271Signature(_)), + "got {err:?}" + ); + } + + #[tokio::test] + async fn enforce_mode_cheap_pass_sim_fail_rejects_with_simulation_failed() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| { + Err(ShadowSimError::Reverted { + reason: "hook reverted: INSUFFICIENT_OUT".into(), + tenderly_url: None, + }) + }); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Enforce, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let err = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await + .unwrap_err(); + match err { + ValidationError::SimulationFailed(reason) => { + assert!(reason.contains("INSUFFICIENT_OUT"), "reason was {reason}"); + } + other => panic!("expected SimulationFailed, got {other:?}"), + } + } + + #[tokio::test] + async fn enforce_mode_cheap_fail_sim_fail_returns_invalid_sig() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Err(SignatureValidationError::Invalid)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| { + Err(ShadowSimError::Reverted { + reason: "x".into(), + tenderly_url: None, + }) + }); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Enforce, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let err = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await + .unwrap_err(); + assert!( + matches!(err, ValidationError::InvalidEip1271Signature(_)), + "got {err:?}" + ); + } + + #[tokio::test] + async fn enforce_mode_cheap_pass_sim_pass_accepts() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| Ok(())); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Enforce, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!(result.is_ok(), "got {result:?}"); + } + + #[tokio::test] + async fn shadow_sim_infra_error_is_fail_open_in_both_modes() { + for mode in [Eip1271ShadowSimMode::Shadow, Eip1271ShadowSimMode::Enforce] { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow + .expect_simulate() + .returning(|_| Err(ShadowSimError::Infra(anyhow!("RPC down")))); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + mode, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!( + result.is_ok(), + "expected Ok for mode={mode:?}, got {result:?}" + ); + } + } + + #[tokio::test] + async fn skip_flag_runs_sim_only_and_never_rejects() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .times(0); + let mut shadow = MockEip1271ShadowSimulator::new(); + shadow.expect_simulate().returning(|_| { + Err(ShadowSimError::Reverted { + reason: "x".into(), + tenderly_url: None, + }) + }); + let validator = build_1271_validator( + signature_validator, + Some(Arc::new(shadow)), + Eip1271ShadowSimMode::Enforce, + DEFAULT_SHADOW_SIM_TIMEOUT, + true, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!(result.is_ok(), "got {result:?}"); + } + + #[tokio::test] + async fn no_shadow_sim_configured_preserves_existing_behaviour() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Err(SignatureValidationError::Invalid)); + let validator = build_1271_validator( + signature_validator, + None, + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + false, + ); + let err = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await + .unwrap_err(); + assert!( + matches!(err, ValidationError::InvalidEip1271Signature(_)), + "got {err:?}" + ); + } } From f0af34751b0ef8b6fe1980b55ca419708111fac9 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:27:19 +0100 Subject: [PATCH 006/154] Add shadow sim mode and timeout to orderbook config --- crates/configs/src/orderbook/mod.rs | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index ef3b6fdb01..0028183d49 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -20,6 +20,7 @@ use { std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, path::Path, + time::Duration, }, }; @@ -59,6 +60,33 @@ pub struct OrderSimulationConfig { /// URL. #[serde(default)] pub tenderly: Option, + + /// Shadow/enforce mode for the EIP-1271 shadow simulation. Defaults to + /// `shadow` (observability only; never rejects). Only set to `enforce` + /// after shadow-mode telemetry shows low-to-zero unexpected + /// disagreements AND flashloan support has landed in the simulator. + #[serde(default)] + pub eip1271_shadow_sim_mode: Eip1271ShadowSimMode, + + /// Per-call timeout for the shadow simulation. Infra errors (including + /// timeouts) never reject the order. Default: 2s. + #[serde(default = "default_shadow_sim_timeout", with = "humantime_serde")] + pub eip1271_shadow_sim_timeout: Duration, +} + +/// Mode for the EIP-1271 shadow simulation. Mirrored by +/// `shared::order_validation::Eip1271ShadowSimMode`; conversion happens at +/// the call site in `orderbook/run.rs`. +#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum Eip1271ShadowSimMode { + #[default] + Shadow, + Enforce, +} + +fn default_shadow_sim_timeout() -> Duration { + Duration::from_secs(2) } /// Top-level orderbook service configuration. @@ -228,6 +256,8 @@ pub mod test_util { order_simulation: Some(OrderSimulationConfig { gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), tenderly: None, + eip1271_shadow_sim_mode: Default::default(), + eip1271_shadow_sim_timeout: std::time::Duration::from_secs(2), }), hide_competition_before_deadline: false, } @@ -438,4 +468,26 @@ mod tests { ); assert_eq!(config.http_client.timeout, deserialized.http_client.timeout) } + + #[test] + fn parses_shadow_sim_mode_default() { + let toml = r#" +gas-limit = "0x1000000" +"#; + let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); + assert_eq!(cfg.eip1271_shadow_sim_mode, Eip1271ShadowSimMode::Shadow); + assert_eq!(cfg.eip1271_shadow_sim_timeout, Duration::from_secs(2)); + } + + #[test] + fn parses_shadow_sim_mode_enforce() { + let toml = r#" +gas-limit = "0x1000000" +eip1271-shadow-sim-mode = "enforce" +eip1271-shadow-sim-timeout = "5s" +"#; + let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); + assert_eq!(cfg.eip1271_shadow_sim_mode, Eip1271ShadowSimMode::Enforce); + assert_eq!(cfg.eip1271_shadow_sim_timeout, Duration::from_secs(5)); + } } From bd51ed83f59b38a5b0c7e157aeb4ae8d72c71f0b Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:29:49 +0100 Subject: [PATCH 007/154] Wire EIP-1271 shadow simulator through orderbook runtime --- crates/orderbook/src/eip1271_shadow_sim.rs | 65 ++++++++++++++++ crates/orderbook/src/lib.rs | 1 + crates/orderbook/src/run.rs | 86 +++++++++++++--------- 3 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 crates/orderbook/src/eip1271_shadow_sim.rs diff --git a/crates/orderbook/src/eip1271_shadow_sim.rs b/crates/orderbook/src/eip1271_shadow_sim.rs new file mode 100644 index 0000000000..a6581b3ac0 --- /dev/null +++ b/crates/orderbook/src/eip1271_shadow_sim.rs @@ -0,0 +1,65 @@ +use { + crate::order_simulator::{self, OrderSimulator}, + async_trait::async_trait, + model::order::Order, + shared::order_validation::{Eip1271ShadowSimulator, ShadowSimError}, + std::sync::Arc, +}; + +/// Adapter exposing `OrderSimulator` via the +/// `shared::order_validation::Eip1271ShadowSimulator` trait. +/// +/// This is a temporary shim. Once the `simulator` crate is refactored to own +/// `OrderSimulator`, `OrderValidator` can depend on it directly and this +/// adapter can be deleted. +pub struct OrderSimulatorAdapter { + inner: Arc, +} + +impl OrderSimulatorAdapter { + pub fn new(inner: Arc) -> Self { + Self { inner } + } +} + +#[async_trait] +impl Eip1271ShadowSimulator for OrderSimulatorAdapter { + async fn simulate(&self, order: &Order) -> Result<(), ShadowSimError> { + let swap = self + .inner + .encode_order(order, Vec::new(), None) + .await + .map_err(map_simulator_err)?; + let result = self + .inner + .simulate_swap(swap, None) + .await + .map_err(map_simulator_err)?; + match result.error { + None => Ok(()), + Some(reason) => Err(ShadowSimError::Reverted { + reason, + tenderly_url: result.tenderly_url, + }), + } + } +} + +fn map_simulator_err(err: order_simulator::Error) -> ShadowSimError { + match err { + order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => { + ShadowSimError::Infra(e) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn impls_trait() { + fn assert_impl() {} + assert_impl::(); + } +} diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index c5c92d4a6a..f953dde97b 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -3,6 +3,7 @@ pub mod app_data; pub mod arguments; pub mod database; pub mod dto; +pub mod eip1271_shadow_sim; mod ipfs; mod ipfs_app_data; pub mod order_simulator; diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 889b5cc3cb..4e00e94891 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -380,6 +380,56 @@ pub async fn run(config: Configuration) { let chainalysis_oracle = ChainalysisOracle::Instance::deployed(&web3.provider) .await .ok(); + + let (order_simulator, shadow_simulator, shadow_sim_mode, shadow_sim_timeout) = + if let Some(sim_config) = config.order_simulation { + let tenderly: Option> = + sim_config.tenderly.as_ref().map(|tenderly_config| { + Box::new(simulator::tenderly::TenderlyApi::new( + tenderly_config, + &http_factory, + chain.id().to_string(), + )) as _ + }); + let order_simulator = Arc::new(OrderSimulator::new( + SwapSimulator::new( + balance_overrider.clone(), + *settlement_contract.address(), + *native_token.address(), + current_block_stream.clone(), + web3, + sim_config + .gas_limit + .try_into() + .expect("gas_limit must fit in u64"), + ) + .await + .expect("failed to create SwapSimulator"), + chain.id().to_string(), + tenderly, + )); + let shadow: Arc = Arc::new( + crate::eip1271_shadow_sim::OrderSimulatorAdapter::new(order_simulator.clone()), + ); + let mode = match sim_config.eip1271_shadow_sim_mode { + configs::orderbook::Eip1271ShadowSimMode::Shadow => Eip1271ShadowSimMode::Shadow, + configs::orderbook::Eip1271ShadowSimMode::Enforce => Eip1271ShadowSimMode::Enforce, + }; + ( + Some(order_simulator), + Some(shadow), + mode, + sim_config.eip1271_shadow_sim_timeout, + ) + } else { + ( + None, + None, + Eip1271ShadowSimMode::Shadow, + DEFAULT_SHADOW_SIM_TIMEOUT, + ) + }; + let order_validator = Arc::new(OrderValidator::new( native_token.clone(), Arc::new(order_validation::banned::Users::new( @@ -394,9 +444,9 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, - None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + shadow_simulator, + shadow_sim_mode, + shadow_sim_timeout, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, @@ -419,36 +469,6 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator = if let Some(config) = config.order_simulation { - let tenderly: Option> = - config.tenderly.as_ref().map(|tenderly_config| { - Box::new(simulator::tenderly::TenderlyApi::new( - tenderly_config, - &http_factory, - chain.id().to_string(), - )) as _ - }); - Some(Arc::new(OrderSimulator::new( - SwapSimulator::new( - balance_overrider.clone(), - *settlement_contract.address(), - *native_token.address(), - current_block_stream.clone(), - web3, - config - .gas_limit - .try_into() - .expect("gas_limit must fit in u64"), - ) - .await - .expect("failed to create SwapSimulator"), - chain.id().to_string(), - tenderly, - ))) - } else { - None - }; - let orderbook = Arc::new(Orderbook::new( domain_separator, *settlement_contract.address(), From 5c6e8d2e69e05a601cce7bae34a3b365bc7152fb Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:40:01 +0100 Subject: [PATCH 008/154] Rename shadow-sim types to drop the shadow prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/configs/src/orderbook/mod.rs | 32 +-- .../{eip1271_shadow_sim.rs => eip1271_sim.rs} | 16 +- crates/orderbook/src/lib.rs | 2 +- crates/orderbook/src/run.rs | 28 +-- crates/shared/src/order_validation.rs | 236 +++++++++--------- 5 files changed, 157 insertions(+), 157 deletions(-) rename crates/orderbook/src/{eip1271_shadow_sim.rs => eip1271_sim.rs} (72%) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 0028183d49..7e611e7fa1 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -66,26 +66,26 @@ pub struct OrderSimulationConfig { /// after shadow-mode telemetry shows low-to-zero unexpected /// disagreements AND flashloan support has landed in the simulator. #[serde(default)] - pub eip1271_shadow_sim_mode: Eip1271ShadowSimMode, + pub eip1271_sim_mode: Eip1271SimMode, /// Per-call timeout for the shadow simulation. Infra errors (including /// timeouts) never reject the order. Default: 2s. - #[serde(default = "default_shadow_sim_timeout", with = "humantime_serde")] - pub eip1271_shadow_sim_timeout: Duration, + #[serde(default = "default_eip1271_sim_timeout", with = "humantime_serde")] + pub eip1271_sim_timeout: Duration, } /// Mode for the EIP-1271 shadow simulation. Mirrored by -/// `shared::order_validation::Eip1271ShadowSimMode`; conversion happens at +/// `shared::order_validation::Eip1271SimMode`; conversion happens at /// the call site in `orderbook/run.rs`. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] -pub enum Eip1271ShadowSimMode { +pub enum Eip1271SimMode { #[default] Shadow, Enforce, } -fn default_shadow_sim_timeout() -> Duration { +fn default_eip1271_sim_timeout() -> Duration { Duration::from_secs(2) } @@ -256,8 +256,8 @@ pub mod test_util { order_simulation: Some(OrderSimulationConfig { gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), tenderly: None, - eip1271_shadow_sim_mode: Default::default(), - eip1271_shadow_sim_timeout: std::time::Duration::from_secs(2), + eip1271_sim_mode: Default::default(), + eip1271_sim_timeout: std::time::Duration::from_secs(2), }), hide_competition_before_deadline: false, } @@ -470,24 +470,24 @@ mod tests { } #[test] - fn parses_shadow_sim_mode_default() { + fn parses_sim_mode_default() { let toml = r#" gas-limit = "0x1000000" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_shadow_sim_mode, Eip1271ShadowSimMode::Shadow); - assert_eq!(cfg.eip1271_shadow_sim_timeout, Duration::from_secs(2)); + assert_eq!(cfg.eip1271_sim_mode, Eip1271SimMode::Shadow); + assert_eq!(cfg.eip1271_sim_timeout, Duration::from_secs(2)); } #[test] - fn parses_shadow_sim_mode_enforce() { + fn parses_sim_mode_enforce() { let toml = r#" gas-limit = "0x1000000" -eip1271-shadow-sim-mode = "enforce" -eip1271-shadow-sim-timeout = "5s" +eip1271-sim-mode = "enforce" +eip1271-sim-timeout = "5s" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_shadow_sim_mode, Eip1271ShadowSimMode::Enforce); - assert_eq!(cfg.eip1271_shadow_sim_timeout, Duration::from_secs(5)); + assert_eq!(cfg.eip1271_sim_mode, Eip1271SimMode::Enforce); + assert_eq!(cfg.eip1271_sim_timeout, Duration::from_secs(5)); } } diff --git a/crates/orderbook/src/eip1271_shadow_sim.rs b/crates/orderbook/src/eip1271_sim.rs similarity index 72% rename from crates/orderbook/src/eip1271_shadow_sim.rs rename to crates/orderbook/src/eip1271_sim.rs index a6581b3ac0..3854359155 100644 --- a/crates/orderbook/src/eip1271_shadow_sim.rs +++ b/crates/orderbook/src/eip1271_sim.rs @@ -2,12 +2,12 @@ use { crate::order_simulator::{self, OrderSimulator}, async_trait::async_trait, model::order::Order, - shared::order_validation::{Eip1271ShadowSimulator, ShadowSimError}, + shared::order_validation::{Eip1271SimError, Eip1271Simulator}, std::sync::Arc, }; /// Adapter exposing `OrderSimulator` via the -/// `shared::order_validation::Eip1271ShadowSimulator` trait. +/// `shared::order_validation::Eip1271Simulator` trait. /// /// This is a temporary shim. Once the `simulator` crate is refactored to own /// `OrderSimulator`, `OrderValidator` can depend on it directly and this @@ -23,8 +23,8 @@ impl OrderSimulatorAdapter { } #[async_trait] -impl Eip1271ShadowSimulator for OrderSimulatorAdapter { - async fn simulate(&self, order: &Order) -> Result<(), ShadowSimError> { +impl Eip1271Simulator for OrderSimulatorAdapter { + async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError> { let swap = self .inner .encode_order(order, Vec::new(), None) @@ -37,7 +37,7 @@ impl Eip1271ShadowSimulator for OrderSimulatorAdapter { .map_err(map_simulator_err)?; match result.error { None => Ok(()), - Some(reason) => Err(ShadowSimError::Reverted { + Some(reason) => Err(Eip1271SimError::Reverted { reason, tenderly_url: result.tenderly_url, }), @@ -45,10 +45,10 @@ impl Eip1271ShadowSimulator for OrderSimulatorAdapter { } } -fn map_simulator_err(err: order_simulator::Error) -> ShadowSimError { +fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimError { match err { order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => { - ShadowSimError::Infra(e) + Eip1271SimError::Infra(e) } } } @@ -59,7 +59,7 @@ mod tests { #[test] fn impls_trait() { - fn assert_impl() {} + fn assert_impl() {} assert_impl::(); } } diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index f953dde97b..83b8779d37 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -3,7 +3,7 @@ pub mod app_data; pub mod arguments; pub mod database; pub mod dto; -pub mod eip1271_shadow_sim; +pub mod eip1271_sim; mod ipfs; mod ipfs_app_data; pub mod order_simulator; diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 4e00e94891..779fb2bb43 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -42,8 +42,8 @@ use { shared::{ order_quoting::{self, OrderQuoter}, order_validation::{ - DEFAULT_SHADOW_SIM_TIMEOUT, - Eip1271ShadowSimMode, + DEFAULT_EIP1271_SIM_TIMEOUT, + Eip1271SimMode, OrderValidPeriodConfiguration, OrderValidator, }, @@ -381,7 +381,7 @@ pub async fn run(config: Configuration) { .await .ok(); - let (order_simulator, shadow_simulator, shadow_sim_mode, shadow_sim_timeout) = + let (order_simulator, eip1271_simulator, eip1271_sim_mode, eip1271_sim_timeout) = if let Some(sim_config) = config.order_simulation { let tenderly: Option> = sim_config.tenderly.as_ref().map(|tenderly_config| { @@ -408,25 +408,25 @@ pub async fn run(config: Configuration) { chain.id().to_string(), tenderly, )); - let shadow: Arc = Arc::new( - crate::eip1271_shadow_sim::OrderSimulatorAdapter::new(order_simulator.clone()), + let shadow: Arc = Arc::new( + crate::eip1271_sim::OrderSimulatorAdapter::new(order_simulator.clone()), ); - let mode = match sim_config.eip1271_shadow_sim_mode { - configs::orderbook::Eip1271ShadowSimMode::Shadow => Eip1271ShadowSimMode::Shadow, - configs::orderbook::Eip1271ShadowSimMode::Enforce => Eip1271ShadowSimMode::Enforce, + let mode = match sim_config.eip1271_sim_mode { + configs::orderbook::Eip1271SimMode::Shadow => Eip1271SimMode::Shadow, + configs::orderbook::Eip1271SimMode::Enforce => Eip1271SimMode::Enforce, }; ( Some(order_simulator), Some(shadow), mode, - sim_config.eip1271_shadow_sim_timeout, + sim_config.eip1271_sim_timeout, ) } else { ( None, None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, ) }; @@ -444,9 +444,9 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, - shadow_simulator, - shadow_sim_mode, - shadow_sim_timeout, + eip1271_simulator, + eip1271_sim_mode, + eip1271_sim_timeout, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index ae481ebc95..01ccc2f12c 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -50,7 +50,7 @@ use { /// Outcome of the shadow simulation against a created order. #[derive(Debug)] -pub enum ShadowSimError { +pub enum Eip1271SimError { /// The simulation ran and the transaction reverted. `reason` is the /// revert string returned by the EVM (or a Tenderly reason string). Reverted { @@ -65,7 +65,7 @@ pub enum ShadowSimError { /// Optional hook used by `OrderValidator` to run a full order simulation /// next to the cheap EIP-1271 signature check. A concrete implementation -/// lives in `crates/orderbook/src/eip1271_shadow_sim.rs`. +/// lives in `crates/orderbook/src/eip1271_sim.rs`. /// /// The trait exists in `shared` because `OrderValidator` lives in `shared` /// and cannot depend upward on `orderbook`. It is designed to be replaced @@ -73,13 +73,13 @@ pub enum ShadowSimError { /// crate is refactored. #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] -pub trait Eip1271ShadowSimulator: Send + Sync { - async fn simulate(&self, order: &Order) -> Result<(), ShadowSimError>; +pub trait Eip1271Simulator: Send + Sync { + async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError>; } /// Mode controlling whether the shadow sim can reject orders. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub enum Eip1271ShadowSimMode { +pub enum Eip1271SimMode { /// Log disagreements, emit metrics. Never reject. **Default.** #[default] Shadow, @@ -90,12 +90,12 @@ pub enum Eip1271ShadowSimMode { } /// Default per-call timeout for the EIP-1271 shadow simulation. Mirrored -/// by `configs::orderbook::default_shadow_sim_timeout` in Task 6. -pub const DEFAULT_SHADOW_SIM_TIMEOUT: Duration = Duration::from_secs(2); +/// by `configs::orderbook::default_eip1271_sim_timeout` in Task 6. +pub const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] -#[metric(subsystem = "eip1271_shadow_sim")] -struct ShadowSimMetrics { +#[metric(subsystem = "eip1271_sim")] +struct Eip1271SimMetrics { /// Shadow sim outcome vs. the cheap check. Labels are each one of /// `pass | fail | infra`, giving a 3x3 confusion matrix. #[metric(labels("cheap", "sim"))] @@ -108,10 +108,10 @@ struct ShadowSimMetrics { duration_seconds: prometheus::Histogram, } -impl ShadowSimMetrics { +impl Eip1271SimMetrics { fn get() -> &'static Self { Self::instance(observe::metrics::get_storage_registry()) - .expect("unexpected error getting ShadowSimMetrics instance") + .expect("unexpected error getting Eip1271SimMetrics instance") } } @@ -160,17 +160,17 @@ fn classify_cheap(res: &Result) -> CheapOutcome { } } -fn classify_sim(res: &Result<(), ShadowSimError>) -> SimOutcome { +fn classify_sim(res: &Result<(), Eip1271SimError>) -> SimOutcome { match res { Ok(()) => SimOutcome::Pass, - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason, tenderly_url, }) => SimOutcome::Fail { reason: reason.clone(), tenderly_url: tenderly_url.clone(), }, - Err(ShadowSimError::Infra(err)) => SimOutcome::Infra(anyhow!("{err}")), + Err(Eip1271SimError::Infra(err)) => SimOutcome::Infra(anyhow!("{err}")), } } @@ -180,7 +180,7 @@ fn record_shadow_outcome( order_uid: OrderUid, owner: Address, ) { - ShadowSimMetrics::get() + Eip1271SimMetrics::get() .total .with_label_values(&[cheap.label(), sim.label()]) .inc(); @@ -204,10 +204,10 @@ fn record_shadow_outcome( sim = sim.label(), reason, ?tenderly_url, - "eip1271 shadow-sim disagreement", + "eip1271 sim disagreement", ); } else if let SimOutcome::Infra(err) = sim { - tracing::warn!(%order_uid, %owner, err = %err, "eip1271 shadow-sim infra error"); + tracing::warn!(%order_uid, %owner, err = %err, "eip1271 sim infra error"); } } @@ -436,9 +436,9 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - shadow_simulator: Option>, - shadow_sim_mode: Eip1271ShadowSimMode, - shadow_sim_timeout: Duration, + eip1271_simulator: Option>, + eip1271_sim_mode: Eip1271SimMode, + eip1271_sim_timeout: Duration, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -509,9 +509,9 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - shadow_simulator: Option>, - shadow_sim_mode: Eip1271ShadowSimMode, - shadow_sim_timeout: Duration, + eip1271_simulator: Option>, + eip1271_sim_mode: Eip1271SimMode, + eip1271_sim_timeout: Duration, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -529,9 +529,9 @@ impl OrderValidator { quoter, balance_fetcher, signature_validator, - shadow_simulator, - shadow_sim_mode, - shadow_sim_timeout, + eip1271_simulator, + eip1271_sim_mode, + eip1271_sim_timeout, limit_order_counter, max_limit_orders_per_user, code_fetcher, @@ -548,8 +548,8 @@ impl OrderValidator { hash: B256, ) -> Result { if self.eip1271_skip_creation_validation { - if let Some(sim) = &self.shadow_simulator { - self.run_shadow_sim_only(sim.as_ref(), preview_order).await; + if let Some(sim) = &self.eip1271_simulator { + self.run_eip1271_sim_only(sim.as_ref(), preview_order).await; } return Ok(0u64); } @@ -558,7 +558,7 @@ impl OrderValidator { .signature_validator .validate_signature_and_get_additional_gas(check); - let Some(sim) = &self.shadow_simulator else { + let Some(sim) = &self.eip1271_simulator else { return cheap_fut.await.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), SignatureValidationError::Other(err) => ValidationError::Other(err), @@ -566,15 +566,15 @@ impl OrderValidator { }; let sim = sim.clone(); - let sim_timeout = self.shadow_sim_timeout; + let sim_timeout = self.eip1271_sim_timeout; let order = preview_order.clone(); let sim_fut = async move { tokio::time::timeout(sim_timeout, sim.simulate(&order)) .await - .unwrap_or_else(|_| Err(ShadowSimError::Infra(anyhow!("shadow sim timeout")))) + .unwrap_or_else(|_| Err(Eip1271SimError::Infra(anyhow!("eip1271 sim timeout")))) }; - let timer = ShadowSimMetrics::get().duration_seconds.start_timer(); + let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); let (cheap_res, sim_res) = tokio::join!(cheap_fut, sim_fut); drop(timer); @@ -587,8 +587,8 @@ impl OrderValidator { preview_order.metadata.owner, ); - match (cheap_res, &sim_outcome, self.shadow_sim_mode) { - (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271ShadowSimMode::Enforce) => { + match (cheap_res, &sim_outcome, self.eip1271_sim_mode) { + (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271SimMode::Enforce) => { Err(ValidationError::SimulationFailed(reason.clone())) } (Ok(gas), _, _) => Ok(gas), @@ -599,23 +599,23 @@ impl OrderValidator { } } - async fn run_shadow_sim_only(&self, sim: &dyn Eip1271ShadowSimulator, preview_order: &Order) { - let timer = ShadowSimMetrics::get().duration_seconds.start_timer(); - let res = tokio::time::timeout(self.shadow_sim_timeout, sim.simulate(preview_order)).await; + async fn run_eip1271_sim_only(&self, sim: &dyn Eip1271Simulator, preview_order: &Order) { + let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); + let res = tokio::time::timeout(self.eip1271_sim_timeout, sim.simulate(preview_order)).await; drop(timer); let outcome = match res { Ok(Ok(())) => SimOutcome::Pass, - Ok(Err(ShadowSimError::Reverted { + Ok(Err(Eip1271SimError::Reverted { reason, tenderly_url, })) => SimOutcome::Fail { reason, tenderly_url, }, - Ok(Err(ShadowSimError::Infra(err))) => SimOutcome::Infra(err), - Err(_) => SimOutcome::Infra(anyhow!("shadow sim timeout")), + Ok(Err(Eip1271SimError::Infra(err))) => SimOutcome::Infra(err), + Err(_) => SimOutcome::Infra(anyhow!("eip1271 sim timeout")), }; - ShadowSimMetrics::get() + Eip1271SimMetrics::get() .sim_only_total .with_label_values(&[outcome.label()]) .inc(); @@ -628,13 +628,13 @@ impl OrderValidator { owner = %preview_order.metadata.owner, reason = %reason, ?tenderly_url, - "eip1271 shadow-sim (cheap check skipped)", + "eip1271 sim (cheap check skipped)", ), SimOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, owner = %preview_order.metadata.owner, err = %err, - "eip1271 shadow-sim infra error (cheap check skipped)", + "eip1271 sim infra error (cheap check skipped)", ), SimOutcome::Pass => {} } @@ -1391,8 +1391,8 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1539,8 +1539,8 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1623,8 +1623,8 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1711,8 +1711,8 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), max_limit_orders_per_user, Arc::new(MockCodeFetching::new()), @@ -1927,8 +1927,8 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -2003,8 +2003,8 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -2067,8 +2067,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2124,8 +2124,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2185,8 +2185,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2249,8 +2249,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2312,8 +2312,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(signature_validator), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2382,8 +2382,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2476,8 +2476,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2891,8 +2891,8 @@ mod tests { Arc::new(balance_fetcher), Arc::new(signature_validating), None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2949,9 +2949,9 @@ mod tests { fn build_1271_validator( signature_validator: MockSignatureValidating, - shadow_simulator: Option>, - shadow_sim_mode: Eip1271ShadowSimMode, - shadow_sim_timeout: Duration, + eip1271_simulator: Option>, + eip1271_sim_mode: Eip1271SimMode, + eip1271_sim_timeout: Duration, eip1271_skip_creation_validation: bool, ) -> OrderValidator { let mut order_quoter = MockOrderQuoting::new(); @@ -2980,9 +2980,9 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validator), - shadow_simulator, - shadow_sim_mode, - shadow_sim_timeout, + eip1271_simulator, + eip1271_sim_mode, + eip1271_sim_timeout, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2993,18 +2993,18 @@ mod tests { } #[tokio::test] - async fn shadow_sim_pass_cheap_pass_accepts_in_shadow_mode() { + async fn shadow_mode_sim_pass_cheap_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let result = validator @@ -3019,14 +3019,14 @@ mod tests { } #[tokio::test] - async fn shadow_sim_fail_cheap_pass_accepts_in_shadow_mode() { + async fn shadow_mode_sim_fail_cheap_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| { - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason: "hook revert".into(), tenderly_url: None, }) @@ -3034,8 +3034,8 @@ mod tests { let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let result = validator @@ -3050,18 +3050,18 @@ mod tests { } #[tokio::test] - async fn shadow_sim_pass_cheap_fail_rejects_with_invalid_sig_in_shadow_mode() { + async fn shadow_mode_sim_pass_cheap_fail_rejects_with_invalid_sig() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let err = validator @@ -3080,14 +3080,14 @@ mod tests { } #[tokio::test] - async fn shadow_sim_fail_cheap_fail_rejects_with_invalid_sig_in_shadow_mode() { + async fn shadow_mode_sim_fail_cheap_fail_rejects_with_invalid_sig() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| { - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, }) @@ -3095,8 +3095,8 @@ mod tests { let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let err = validator @@ -3120,9 +3120,9 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| { - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason: "hook reverted: INSUFFICIENT_OUT".into(), tenderly_url: None, }) @@ -3130,8 +3130,8 @@ mod tests { let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Enforce, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Enforce, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let err = validator @@ -3157,9 +3157,9 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| { - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, }) @@ -3167,8 +3167,8 @@ mod tests { let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Enforce, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Enforce, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let err = validator @@ -3192,13 +3192,13 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Enforce, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Enforce, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let result = validator @@ -3213,21 +3213,21 @@ mod tests { } #[tokio::test] - async fn shadow_sim_infra_error_is_fail_open_in_both_modes() { - for mode in [Eip1271ShadowSimMode::Shadow, Eip1271ShadowSimMode::Enforce] { + async fn sim_infra_error_is_fail_open_in_both_modes() { + for mode in [Eip1271SimMode::Shadow, Eip1271SimMode::Enforce] { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow .expect_simulate() - .returning(|_| Err(ShadowSimError::Infra(anyhow!("RPC down")))); + .returning(|_| Err(Eip1271SimError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), mode, - DEFAULT_SHADOW_SIM_TIMEOUT, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let result = validator @@ -3251,9 +3251,9 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); - let mut shadow = MockEip1271ShadowSimulator::new(); + let mut shadow = MockEip1271Simulator::new(); shadow.expect_simulate().returning(|_| { - Err(ShadowSimError::Reverted { + Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, }) @@ -3261,8 +3261,8 @@ mod tests { let validator = build_1271_validator( signature_validator, Some(Arc::new(shadow)), - Eip1271ShadowSimMode::Enforce, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Enforce, + DEFAULT_EIP1271_SIM_TIMEOUT, true, ); let result = validator @@ -3277,7 +3277,7 @@ mod tests { } #[tokio::test] - async fn no_shadow_sim_configured_preserves_existing_behaviour() { + async fn no_sim_configured_preserves_existing_behaviour() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3285,8 +3285,8 @@ mod tests { let validator = build_1271_validator( signature_validator, None, - Eip1271ShadowSimMode::Shadow, - DEFAULT_SHADOW_SIM_TIMEOUT, + Eip1271SimMode::Shadow, + DEFAULT_EIP1271_SIM_TIMEOUT, false, ); let err = validator From 557ae354fee98eb69d1efed67b3eb9270cf64548 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:45:03 +0100 Subject: [PATCH 009/154] Clean up remaining 'shadow' references in docs and locals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- crates/configs/src/orderbook/mod.rs | 10 ++-- crates/orderbook/src/run.rs | 4 +- crates/shared/src/order_validation.rs | 82 +++++++++++++-------------- 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 7e611e7fa1..8a08ffdddd 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -61,20 +61,20 @@ pub struct OrderSimulationConfig { #[serde(default)] pub tenderly: Option, - /// Shadow/enforce mode for the EIP-1271 shadow simulation. Defaults to + /// Mode for the EIP-1271 order simulation. Defaults to /// `shadow` (observability only; never rejects). Only set to `enforce` - /// after shadow-mode telemetry shows low-to-zero unexpected + /// after Shadow-mode telemetry shows low-to-zero unexpected /// disagreements AND flashloan support has landed in the simulator. #[serde(default)] pub eip1271_sim_mode: Eip1271SimMode, - /// Per-call timeout for the shadow simulation. Infra errors (including - /// timeouts) never reject the order. Default: 2s. + /// Per-call timeout for the EIP-1271 order simulation. Infra errors + /// (including timeouts) never reject the order. Default: 2s. #[serde(default = "default_eip1271_sim_timeout", with = "humantime_serde")] pub eip1271_sim_timeout: Duration, } -/// Mode for the EIP-1271 shadow simulation. Mirrored by +/// Mode for the EIP-1271 order simulation. Mirrored by /// `shared::order_validation::Eip1271SimMode`; conversion happens at /// the call site in `orderbook/run.rs`. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 779fb2bb43..8363c1a958 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -408,7 +408,7 @@ pub async fn run(config: Configuration) { chain.id().to_string(), tenderly, )); - let shadow: Arc = Arc::new( + let sim: Arc = Arc::new( crate::eip1271_sim::OrderSimulatorAdapter::new(order_simulator.clone()), ); let mode = match sim_config.eip1271_sim_mode { @@ -417,7 +417,7 @@ pub async fn run(config: Configuration) { }; ( Some(order_simulator), - Some(shadow), + Some(sim), mode, sim_config.eip1271_sim_timeout, ) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 01ccc2f12c..81bc07e811 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -48,7 +48,7 @@ use { tracing::instrument, }; -/// Outcome of the shadow simulation against a created order. +/// Outcome of the EIP-1271 order simulation. #[derive(Debug)] pub enum Eip1271SimError { /// The simulation ran and the transaction reverted. `reason` is the @@ -58,7 +58,7 @@ pub enum Eip1271SimError { tenderly_url: Option, }, /// The simulation could not run (RPC failure, Tenderly error, malformed - /// input, timeout). Treated as fail-open in both shadow and enforce + /// input, timeout). Treated as fail-open in both Shadow and Enforce /// modes. Infra(anyhow::Error), } @@ -77,34 +77,34 @@ pub trait Eip1271Simulator: Send + Sync { async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError>; } -/// Mode controlling whether the shadow sim can reject orders. +/// Mode controlling whether the EIP-1271 order simulation can reject orders. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub enum Eip1271SimMode { /// Log disagreements, emit metrics. Never reject. **Default.** #[default] Shadow, - /// If the cheap check passes but the shadow sim fails, reject the + /// If the cheap check passes but the order simulation fails, reject the /// order with `ValidationError::SimulationFailed`. Infra errors still /// never reject (fail-open). Enforce, } -/// Default per-call timeout for the EIP-1271 shadow simulation. Mirrored +/// Default per-call timeout for the EIP-1271 order simulation. Mirrored /// by `configs::orderbook::default_eip1271_sim_timeout` in Task 6. pub const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] #[metric(subsystem = "eip1271_sim")] struct Eip1271SimMetrics { - /// Shadow sim outcome vs. the cheap check. Labels are each one of + /// Sim outcome vs. the cheap check. Labels are each one of /// `pass | fail | infra`, giving a 3x3 confusion matrix. #[metric(labels("cheap", "sim"))] total: prometheus::IntCounterVec, - /// Shadow sim outcome when the cheap check was skipped via + /// Sim outcome when the cheap check was skipped via /// `eip1271_skip_creation_validation`. Label is `pass | fail | infra`. #[metric(labels("sim"))] sim_only_total: prometheus::IntCounterVec, - /// Duration of the shadow simulation. + /// Duration of the EIP-1271 order simulation. duration_seconds: prometheus::Histogram, } @@ -174,12 +174,7 @@ fn classify_sim(res: &Result<(), Eip1271SimError>) -> SimOutcome { } } -fn record_shadow_outcome( - cheap: CheapOutcome, - sim: &SimOutcome, - order_uid: OrderUid, - owner: Address, -) { +fn record_sim_outcome(cheap: CheapOutcome, sim: &SimOutcome, order_uid: OrderUid, owner: Address) { Eip1271SimMetrics::get() .total .with_label_values(&[cheap.label(), sim.label()]) @@ -335,7 +330,7 @@ pub enum ValidationError { /// An invalid EIP-1271 signature, where the on-chain validation check /// reverted or did not return the expected value. InvalidEip1271Signature(B256), - /// The shadow simulation returned a revert in enforce mode. Only + /// The EIP-1271 order simulation returned a revert in enforce mode. Only /// possible when the cheap 1271 signature check passed but the full /// order simulation failed. The Tenderly URL, when available, is /// logged separately and is intentionally not surfaced here. @@ -580,7 +575,7 @@ impl OrderValidator { let cheap_outcome = classify_cheap(&cheap_res); let sim_outcome = classify_sim(&sim_res); - record_shadow_outcome( + record_sim_outcome( cheap_outcome, &sim_outcome, preview_order.metadata.uid, @@ -2998,11 +2993,11 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| Ok(())); + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Shadow, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3024,8 +3019,8 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| { + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "hook revert".into(), tenderly_url: None, @@ -3033,7 +3028,7 @@ mod tests { }); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Shadow, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3055,11 +3050,11 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| Ok(())); + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Shadow, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3085,8 +3080,8 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| { + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, @@ -3094,7 +3089,7 @@ mod tests { }); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Shadow, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3120,8 +3115,8 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| { + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "hook reverted: INSUFFICIENT_OUT".into(), tenderly_url: None, @@ -3129,7 +3124,7 @@ mod tests { }); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Enforce, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3157,8 +3152,8 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| { + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, @@ -3166,7 +3161,7 @@ mod tests { }); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Enforce, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3192,11 +3187,11 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| Ok(())); + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Enforce, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3219,13 +3214,12 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut shadow = MockEip1271Simulator::new(); - shadow - .expect_simulate() + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate() .returning(|_| Err(Eip1271SimError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), mode, DEFAULT_EIP1271_SIM_TIMEOUT, false, @@ -3251,8 +3245,8 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); - let mut shadow = MockEip1271Simulator::new(); - shadow.expect_simulate().returning(|_| { + let mut sim = MockEip1271Simulator::new(); + sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), tenderly_url: None, @@ -3260,7 +3254,7 @@ mod tests { }); let validator = build_1271_validator( signature_validator, - Some(Arc::new(shadow)), + Some(Arc::new(sim)), Eip1271SimMode::Enforce, DEFAULT_EIP1271_SIM_TIMEOUT, true, From 6e7ee7a0ffdc6dfb3cfc2c3f90b663a0a0d8bfbe Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 18:54:02 +0100 Subject: [PATCH 010/154] Nits --- crates/configs/src/orderbook/mod.rs | 11 +++-------- crates/orderbook/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 8a08ffdddd..3197e57151 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -61,22 +61,17 @@ pub struct OrderSimulationConfig { #[serde(default)] pub tenderly: Option, - /// Mode for the EIP-1271 order simulation. Defaults to - /// `shadow` (observability only; never rejects). Only set to `enforce` - /// after Shadow-mode telemetry shows low-to-zero unexpected - /// disagreements AND flashloan support has landed in the simulator. + /// Mode for the EIP-1271 order simulation. #[serde(default)] pub eip1271_sim_mode: Eip1271SimMode, - /// Per-call timeout for the EIP-1271 order simulation. Infra errors - /// (including timeouts) never reject the order. Default: 2s. + /// Per-call timeout for the EIP-1271 order simulation. #[serde(default = "default_eip1271_sim_timeout", with = "humantime_serde")] pub eip1271_sim_timeout: Duration, } /// Mode for the EIP-1271 order simulation. Mirrored by -/// `shared::order_validation::Eip1271SimMode`; conversion happens at -/// the call site in `orderbook/run.rs`. +/// `shared::order_validation::Eip1271SimMode`. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Eip1271SimMode { diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index 83b8779d37..267135811d 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -3,7 +3,7 @@ pub mod app_data; pub mod arguments; pub mod database; pub mod dto; -pub mod eip1271_sim; +mod eip1271_sim; mod ipfs; mod ipfs_app_data; pub mod order_simulator; From 1733fb239b7fd5806100850e904098505ab6ccb8 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:01:17 +0100 Subject: [PATCH 011/154] Group EIP-1271 sim deps into Eip1271SimConfig on OrderValidator The simulator, mode, and timeout are only meaningful together. Collapsing them into a single Option 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. --- crates/orderbook/src/run.rs | 31 +++-- crates/shared/src/order_validation.rs | 156 +++++++++++--------------- 2 files changed, 81 insertions(+), 106 deletions(-) diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 8363c1a958..ef61ff0db9 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -42,7 +42,7 @@ use { shared::{ order_quoting::{self, OrderQuoter}, order_validation::{ - DEFAULT_EIP1271_SIM_TIMEOUT, + Eip1271SimConfig, Eip1271SimMode, OrderValidPeriodConfiguration, OrderValidator, @@ -381,8 +381,8 @@ pub async fn run(config: Configuration) { .await .ok(); - let (order_simulator, eip1271_simulator, eip1271_sim_mode, eip1271_sim_timeout) = - if let Some(sim_config) = config.order_simulation { + let (order_simulator, eip1271_sim) = match config.order_simulation { + Some(sim_config) => { let tenderly: Option> = sim_config.tenderly.as_ref().map(|tenderly_config| { Box::new(simulator::tenderly::TenderlyApi::new( @@ -408,7 +408,7 @@ pub async fn run(config: Configuration) { chain.id().to_string(), tenderly, )); - let sim: Arc = Arc::new( + let simulator: Arc = Arc::new( crate::eip1271_sim::OrderSimulatorAdapter::new(order_simulator.clone()), ); let mode = match sim_config.eip1271_sim_mode { @@ -417,18 +417,15 @@ pub async fn run(config: Configuration) { }; ( Some(order_simulator), - Some(sim), - mode, - sim_config.eip1271_sim_timeout, + Some(Eip1271SimConfig { + simulator, + mode, + timeout: sim_config.eip1271_sim_timeout, + }), ) - } else { - ( - None, - None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - ) - }; + } + None => (None, None), + }; let order_validator = Arc::new(OrderValidator::new( native_token.clone(), @@ -444,9 +441,7 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, - eip1271_simulator, - eip1271_sim_mode, - eip1271_sim_timeout, + eip1271_sim, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 81bc07e811..fe3ad6260e 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -90,9 +90,20 @@ pub enum Eip1271SimMode { } /// Default per-call timeout for the EIP-1271 order simulation. Mirrored -/// by `configs::orderbook::default_eip1271_sim_timeout` in Task 6. +/// by `configs::orderbook::default_eip1271_sim_timeout`. pub const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); +/// Bundle of dependencies that enable running the EIP-1271 order +/// simulation alongside the cheap signature check. All three fields must +/// be present together — `mode` and `timeout` are only meaningful when +/// `simulator` is available. +#[derive(Clone)] +pub struct Eip1271SimConfig { + pub simulator: Arc, + pub mode: Eip1271SimMode, + pub timeout: Duration, +} + #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] #[metric(subsystem = "eip1271_sim")] struct Eip1271SimMetrics { @@ -206,6 +217,47 @@ fn record_sim_outcome(cheap: CheapOutcome, sim: &SimOutcome, order_uid: OrderUid } } +async fn run_eip1271_sim_only(config: &Eip1271SimConfig, preview_order: &Order) { + let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); + let res = tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await; + drop(timer); + let outcome = match res { + Ok(Ok(())) => SimOutcome::Pass, + Ok(Err(Eip1271SimError::Reverted { + reason, + tenderly_url, + })) => SimOutcome::Fail { + reason, + tenderly_url, + }, + Ok(Err(Eip1271SimError::Infra(err))) => SimOutcome::Infra(err), + Err(_) => SimOutcome::Infra(anyhow!("eip1271 sim timeout")), + }; + Eip1271SimMetrics::get() + .sim_only_total + .with_label_values(&[outcome.label()]) + .inc(); + match &outcome { + SimOutcome::Fail { + reason, + tenderly_url, + } => tracing::info!( + order_uid = %preview_order.metadata.uid, + owner = %preview_order.metadata.owner, + reason = %reason, + ?tenderly_url, + "eip1271 sim (cheap check skipped)", + ), + SimOutcome::Infra(err) => tracing::warn!( + order_uid = %preview_order.metadata.uid, + owner = %preview_order.metadata.owner, + err = %err, + "eip1271 sim infra error (cheap check skipped)", + ), + SimOutcome::Pass => {} + } +} + fn build_preview_order_for_sim( data: &OrderData, interactions: &Interactions, @@ -431,9 +483,7 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_simulator: Option>, - eip1271_sim_mode: Eip1271SimMode, - eip1271_sim_timeout: Duration, + eip1271_sim: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -504,9 +554,7 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_simulator: Option>, - eip1271_sim_mode: Eip1271SimMode, - eip1271_sim_timeout: Duration, + eip1271_sim: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -524,9 +572,7 @@ impl OrderValidator { quoter, balance_fetcher, signature_validator, - eip1271_simulator, - eip1271_sim_mode, - eip1271_sim_timeout, + eip1271_sim, limit_order_counter, max_limit_orders_per_user, code_fetcher, @@ -543,8 +589,8 @@ impl OrderValidator { hash: B256, ) -> Result { if self.eip1271_skip_creation_validation { - if let Some(sim) = &self.eip1271_simulator { - self.run_eip1271_sim_only(sim.as_ref(), preview_order).await; + if let Some(config) = &self.eip1271_sim { + run_eip1271_sim_only(config, preview_order).await; } return Ok(0u64); } @@ -553,15 +599,15 @@ impl OrderValidator { .signature_validator .validate_signature_and_get_additional_gas(check); - let Some(sim) = &self.eip1271_simulator else { + let Some(config) = &self.eip1271_sim else { return cheap_fut.await.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), SignatureValidationError::Other(err) => ValidationError::Other(err), }); }; - let sim = sim.clone(); - let sim_timeout = self.eip1271_sim_timeout; + let sim = config.simulator.clone(); + let sim_timeout = config.timeout; let order = preview_order.clone(); let sim_fut = async move { tokio::time::timeout(sim_timeout, sim.simulate(&order)) @@ -582,7 +628,7 @@ impl OrderValidator { preview_order.metadata.owner, ); - match (cheap_res, &sim_outcome, self.eip1271_sim_mode) { + match (cheap_res, &sim_outcome, config.mode) { (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271SimMode::Enforce) => { Err(ValidationError::SimulationFailed(reason.clone())) } @@ -594,47 +640,6 @@ impl OrderValidator { } } - async fn run_eip1271_sim_only(&self, sim: &dyn Eip1271Simulator, preview_order: &Order) { - let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); - let res = tokio::time::timeout(self.eip1271_sim_timeout, sim.simulate(preview_order)).await; - drop(timer); - let outcome = match res { - Ok(Ok(())) => SimOutcome::Pass, - Ok(Err(Eip1271SimError::Reverted { - reason, - tenderly_url, - })) => SimOutcome::Fail { - reason, - tenderly_url, - }, - Ok(Err(Eip1271SimError::Infra(err))) => SimOutcome::Infra(err), - Err(_) => SimOutcome::Infra(anyhow!("eip1271 sim timeout")), - }; - Eip1271SimMetrics::get() - .sim_only_total - .with_label_values(&[outcome.label()]) - .inc(); - match &outcome { - SimOutcome::Fail { - reason, - tenderly_url, - } => tracing::info!( - order_uid = %preview_order.metadata.uid, - owner = %preview_order.metadata.owner, - reason = %reason, - ?tenderly_url, - "eip1271 sim (cheap check skipped)", - ), - SimOutcome::Infra(err) => tracing::warn!( - order_uid = %preview_order.metadata.uid, - owner = %preview_order.metadata.owner, - err = %err, - "eip1271 sim infra error (cheap check skipped)", - ), - SimOutcome::Pass => {} - } - } - async fn check_max_limit_orders(&self, owner: Address) -> Result<(), ValidationError> { let num_limit_orders = self .limit_order_counter @@ -1386,8 +1391,6 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1534,8 +1537,6 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1618,8 +1619,6 @@ mod tests { Arc::new(MockBalanceFetching::new()), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -1706,8 +1705,6 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), max_limit_orders_per_user, Arc::new(MockCodeFetching::new()), @@ -1922,8 +1919,6 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -1998,8 +1993,6 @@ mod tests { Arc::new(balance_fetcher), signature_validating, None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), MAX_LIMIT_ORDERS_PER_USER, Arc::new(MockCodeFetching::new()), @@ -2062,8 +2055,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2119,8 +2110,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2180,8 +2169,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2244,8 +2231,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2307,8 +2292,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(signature_validator), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2377,8 +2360,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2471,8 +2452,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(MockSignatureValidating::new()), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2886,8 +2865,6 @@ mod tests { Arc::new(balance_fetcher), Arc::new(signature_validating), None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2960,6 +2937,11 @@ mod tests { let mut limit_order_counter = MockLimitOrderCounting::new(); limit_order_counter.expect_count().returning(|_| Ok(0u64)); let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); + let eip1271_sim = eip1271_simulator.map(|simulator| Eip1271SimConfig { + simulator, + mode: eip1271_sim_mode, + timeout: eip1271_sim_timeout, + }); OrderValidator::new( native_token, Arc::new(order_validation::banned::Users::none()), @@ -2975,9 +2957,7 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validator), - eip1271_simulator, - eip1271_sim_mode, - eip1271_sim_timeout, + eip1271_sim, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), From af31a39439318dd667488ba872d0684bdfbb59e6 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:04:26 +0100 Subject: [PATCH 012/154] Rename Eip1271Simulator trait to Eip1271Simulating; promote SimConfig 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. --- crates/orderbook/src/eip1271_sim.rs | 8 +++---- crates/orderbook/src/run.rs | 6 ++--- crates/shared/src/order_validation.rs | 34 +++++++++++++-------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/orderbook/src/eip1271_sim.rs b/crates/orderbook/src/eip1271_sim.rs index 3854359155..c58a5409d2 100644 --- a/crates/orderbook/src/eip1271_sim.rs +++ b/crates/orderbook/src/eip1271_sim.rs @@ -2,12 +2,12 @@ use { crate::order_simulator::{self, OrderSimulator}, async_trait::async_trait, model::order::Order, - shared::order_validation::{Eip1271SimError, Eip1271Simulator}, + shared::order_validation::{Eip1271SimError, Eip1271Simulating}, std::sync::Arc, }; /// Adapter exposing `OrderSimulator` via the -/// `shared::order_validation::Eip1271Simulator` trait. +/// `shared::order_validation::Eip1271Simulating` trait. /// /// This is a temporary shim. Once the `simulator` crate is refactored to own /// `OrderSimulator`, `OrderValidator` can depend on it directly and this @@ -23,7 +23,7 @@ impl OrderSimulatorAdapter { } #[async_trait] -impl Eip1271Simulator for OrderSimulatorAdapter { +impl Eip1271Simulating for OrderSimulatorAdapter { async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError> { let swap = self .inner @@ -59,7 +59,7 @@ mod tests { #[test] fn impls_trait() { - fn assert_impl() {} + fn assert_impl() {} assert_impl::(); } } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index ef61ff0db9..4ee95ec26e 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -42,8 +42,8 @@ use { shared::{ order_quoting::{self, OrderQuoter}, order_validation::{ - Eip1271SimConfig, Eip1271SimMode, + Eip1271Simulator, OrderValidPeriodConfiguration, OrderValidator, }, @@ -408,7 +408,7 @@ pub async fn run(config: Configuration) { chain.id().to_string(), tenderly, )); - let simulator: Arc = Arc::new( + let simulator: Arc = Arc::new( crate::eip1271_sim::OrderSimulatorAdapter::new(order_simulator.clone()), ); let mode = match sim_config.eip1271_sim_mode { @@ -417,7 +417,7 @@ pub async fn run(config: Configuration) { }; ( Some(order_simulator), - Some(Eip1271SimConfig { + Some(Eip1271Simulator { simulator, mode, timeout: sim_config.eip1271_sim_timeout, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index fe3ad6260e..0c5dbf2119 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -73,7 +73,7 @@ pub enum Eip1271SimError { /// crate is refactored. #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] -pub trait Eip1271Simulator: Send + Sync { +pub trait Eip1271Simulating: Send + Sync { async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError>; } @@ -98,8 +98,8 @@ pub const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); /// be present together — `mode` and `timeout` are only meaningful when /// `simulator` is available. #[derive(Clone)] -pub struct Eip1271SimConfig { - pub simulator: Arc, +pub struct Eip1271Simulator { + pub simulator: Arc, pub mode: Eip1271SimMode, pub timeout: Duration, } @@ -217,7 +217,7 @@ fn record_sim_outcome(cheap: CheapOutcome, sim: &SimOutcome, order_uid: OrderUid } } -async fn run_eip1271_sim_only(config: &Eip1271SimConfig, preview_order: &Order) { +async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) { let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); let res = tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await; drop(timer); @@ -483,7 +483,7 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_sim: Option, + eip1271_sim: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -554,7 +554,7 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_sim: Option, + eip1271_sim: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -2921,7 +2921,7 @@ mod tests { fn build_1271_validator( signature_validator: MockSignatureValidating, - eip1271_simulator: Option>, + eip1271_simulator: Option>, eip1271_sim_mode: Eip1271SimMode, eip1271_sim_timeout: Duration, eip1271_skip_creation_validation: bool, @@ -2937,7 +2937,7 @@ mod tests { let mut limit_order_counter = MockLimitOrderCounting::new(); limit_order_counter.expect_count().returning(|_| Ok(0u64)); let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); - let eip1271_sim = eip1271_simulator.map(|simulator| Eip1271SimConfig { + let eip1271_sim = eip1271_simulator.map(|simulator| Eip1271Simulator { simulator, mode: eip1271_sim_mode, timeout: eip1271_sim_timeout, @@ -2973,7 +2973,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, @@ -2999,7 +2999,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "hook revert".into(), @@ -3030,7 +3030,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, @@ -3060,7 +3060,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), @@ -3095,7 +3095,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "hook reverted: INSUFFICIENT_OUT".into(), @@ -3132,7 +3132,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), @@ -3167,7 +3167,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); let validator = build_1271_validator( signature_validator, @@ -3194,7 +3194,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate() .returning(|_| Err(Eip1271SimError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( @@ -3225,7 +3225,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); - let mut sim = MockEip1271Simulator::new(); + let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { Err(Eip1271SimError::Reverted { reason: "x".into(), From 65a0290a99b4935e00bd5a2061dfc2777e715e70 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:07:09 +0100 Subject: [PATCH 013/154] Collapse build_1271_validator args to take Option 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. --- crates/shared/src/order_validation.rs | 113 +++++++++----------------- 1 file changed, 39 insertions(+), 74 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 0c5dbf2119..229331aeaf 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -2919,11 +2919,25 @@ mod tests { } } + fn shadow_mode_sim(sim: MockEip1271Simulating) -> Eip1271Simulator { + Eip1271Simulator { + simulator: Arc::new(sim), + mode: Eip1271SimMode::Shadow, + timeout: DEFAULT_EIP1271_SIM_TIMEOUT, + } + } + + fn enforce_mode_sim(sim: MockEip1271Simulating) -> Eip1271Simulator { + Eip1271Simulator { + simulator: Arc::new(sim), + mode: Eip1271SimMode::Enforce, + timeout: DEFAULT_EIP1271_SIM_TIMEOUT, + } + } + fn build_1271_validator( signature_validator: MockSignatureValidating, - eip1271_simulator: Option>, - eip1271_sim_mode: Eip1271SimMode, - eip1271_sim_timeout: Duration, + eip1271_sim: Option, eip1271_skip_creation_validation: bool, ) -> OrderValidator { let mut order_quoter = MockOrderQuoting::new(); @@ -2937,11 +2951,6 @@ mod tests { let mut limit_order_counter = MockLimitOrderCounting::new(); limit_order_counter.expect_count().returning(|_| Ok(0u64)); let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); - let eip1271_sim = eip1271_simulator.map(|simulator| Eip1271Simulator { - simulator, - mode: eip1271_sim_mode, - timeout: eip1271_sim_timeout, - }); OrderValidator::new( native_token, Arc::new(order_validation::banned::Users::none()), @@ -2975,13 +2984,8 @@ mod tests { .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3006,13 +3010,8 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3032,13 +3031,8 @@ mod tests { .returning(|_| Err(SignatureValidationError::Invalid)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3067,13 +3061,8 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3102,13 +3091,8 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Enforce, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3139,13 +3123,8 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Enforce, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3169,13 +3148,8 @@ mod tests { .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Enforce, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = + build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3199,9 +3173,11 @@ mod tests { .returning(|_| Err(Eip1271SimError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( signature_validator, - Some(Arc::new(sim)), - mode, - DEFAULT_EIP1271_SIM_TIMEOUT, + Some(Eip1271Simulator { + simulator: Arc::new(sim), + mode, + timeout: DEFAULT_EIP1271_SIM_TIMEOUT, + }), false, ); let result = validator @@ -3232,13 +3208,8 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(Arc::new(sim)), - Eip1271SimMode::Enforce, - DEFAULT_EIP1271_SIM_TIMEOUT, - true, - ); + let validator = + build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), true); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3256,13 +3227,7 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); - let validator = build_1271_validator( - signature_validator, - None, - Eip1271SimMode::Shadow, - DEFAULT_EIP1271_SIM_TIMEOUT, - false, - ); + let validator = build_1271_validator(signature_validator, None, false); let err = validator .validate_and_construct_order( make_1271_order_creation(), From a37c7a0817d98f093c41432f8be1e80be984b96e Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:08:46 +0100 Subject: [PATCH 014/154] Move DEFAULT_EIP1271_SIM_TIMEOUT into test module 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. --- crates/shared/src/order_validation.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 229331aeaf..3689c07c62 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -89,10 +89,6 @@ pub enum Eip1271SimMode { Enforce, } -/// Default per-call timeout for the EIP-1271 order simulation. Mirrored -/// by `configs::orderbook::default_eip1271_sim_timeout`. -pub const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); - /// Bundle of dependencies that enable running the EIP-1271 order /// simulation alongside the cheap signature check. All three fields must /// be present together — `mode` and `timeout` are only meaningful when @@ -1362,6 +1358,8 @@ mod tests { signature_validator::MockSignatureValidating, }; + const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); + #[tokio::test] async fn pre_validate_err() { let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); From 9c93de6cb157ac6ae3c33013e502c49d6f4ecded Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:09:11 +0100 Subject: [PATCH 015/154] Simplify Eip1271Simulator doc comment --- crates/shared/src/order_validation.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 3689c07c62..e988ece609 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -89,10 +89,9 @@ pub enum Eip1271SimMode { Enforce, } -/// Bundle of dependencies that enable running the EIP-1271 order -/// simulation alongside the cheap signature check. All three fields must -/// be present together — `mode` and `timeout` are only meaningful when -/// `simulator` is available. +/// Runs a full order simulation alongside the cheap EIP-1271 signature +/// check and decides, based on the configured mode, whether a failure +/// should reject the order. #[derive(Clone)] pub struct Eip1271Simulator { pub simulator: Arc, From f5ddba53d5b6f66d893d2ffe7e4405b6c4f01e77 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:16:11 +0100 Subject: [PATCH 016/154] =?UTF-8?q?Rename=20cheap=E2=86=92signature=20in?= =?UTF-8?q?=20sim=20metrics;=20merge=20sim=5Fonly=5Ftotal=20into=20total?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/shared/src/order_validation.rs | 75 +++++++++++++++------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index e988ece609..097992b09c 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -83,7 +83,7 @@ pub enum Eip1271SimMode { /// Log disagreements, emit metrics. Never reject. **Default.** #[default] Shadow, - /// If the cheap check passes but the order simulation fails, reject the + /// If the signature check passes but the order simulation fails, reject the /// order with `ValidationError::SimulationFailed`. Infra errors still /// never reject (fail-open). Enforce, @@ -102,14 +102,11 @@ pub struct Eip1271Simulator { #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] #[metric(subsystem = "eip1271_sim")] struct Eip1271SimMetrics { - /// Sim outcome vs. the cheap check. Labels are each one of - /// `pass | fail | infra`, giving a 3x3 confusion matrix. - #[metric(labels("cheap", "sim"))] + /// Counts each (signature-check, sim) outcome pair. The signature + /// axis takes `pass | fail | infra | skipped`; the sim axis takes + /// `pass | fail | infra`. + #[metric(labels("signature", "sim"))] total: prometheus::IntCounterVec, - /// Sim outcome when the cheap check was skipped via - /// `eip1271_skip_creation_validation`. Label is `pass | fail | infra`. - #[metric(labels("sim"))] - sim_only_total: prometheus::IntCounterVec, /// Duration of the EIP-1271 order simulation. duration_seconds: prometheus::Histogram, } @@ -122,10 +119,13 @@ impl Eip1271SimMetrics { } #[derive(Copy, Clone, Debug)] -enum CheapOutcome { +enum SignatureOutcome { Pass, Fail, Infra, + /// `eip1271_skip_creation_validation` is set — the signature check was + /// not run, only the simulation was. + Skipped, } #[derive(Debug)] @@ -138,12 +138,13 @@ enum SimOutcome { Infra(anyhow::Error), } -impl CheapOutcome { +impl SignatureOutcome { fn label(&self) -> &'static str { match self { Self::Pass => "pass", Self::Fail => "fail", Self::Infra => "infra", + Self::Skipped => "skipped", } } } @@ -158,11 +159,11 @@ impl SimOutcome { } } -fn classify_cheap(res: &Result) -> CheapOutcome { +fn classify_signature(res: &Result) -> SignatureOutcome { match res { - Ok(_) => CheapOutcome::Pass, - Err(SignatureValidationError::Invalid) => CheapOutcome::Fail, - Err(SignatureValidationError::Other(_)) => CheapOutcome::Infra, + Ok(_) => SignatureOutcome::Pass, + Err(SignatureValidationError::Invalid) => SignatureOutcome::Fail, + Err(SignatureValidationError::Other(_)) => SignatureOutcome::Infra, } } @@ -180,7 +181,12 @@ fn classify_sim(res: &Result<(), Eip1271SimError>) -> SimOutcome { } } -fn record_sim_outcome(cheap: CheapOutcome, sim: &SimOutcome, order_uid: OrderUid, owner: Address) { +fn record_sim_outcome( + cheap: SignatureOutcome, + sim: &SimOutcome, + order_uid: OrderUid, + owner: Address, +) { Eip1271SimMetrics::get() .total .with_label_values(&[cheap.label(), sim.label()]) @@ -188,7 +194,8 @@ fn record_sim_outcome(cheap: CheapOutcome, sim: &SimOutcome, order_uid: OrderUid let disagreement = matches!( (&cheap, sim), - (CheapOutcome::Pass, SimOutcome::Fail { .. }) | (CheapOutcome::Fail, SimOutcome::Pass) + (SignatureOutcome::Pass, SimOutcome::Fail { .. }) + | (SignatureOutcome::Fail, SimOutcome::Pass) ); if disagreement { let (reason, tenderly_url) = match sim { @@ -229,8 +236,8 @@ async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) Err(_) => SimOutcome::Infra(anyhow!("eip1271 sim timeout")), }; Eip1271SimMetrics::get() - .sim_only_total - .with_label_values(&[outcome.label()]) + .total + .with_label_values(&[SignatureOutcome::Skipped.label(), outcome.label()]) .inc(); match &outcome { SimOutcome::Fail { @@ -241,13 +248,13 @@ async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) owner = %preview_order.metadata.owner, reason = %reason, ?tenderly_url, - "eip1271 sim (cheap check skipped)", + "eip1271 sim (signature check skipped)", ), SimOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, owner = %preview_order.metadata.owner, err = %err, - "eip1271 sim infra error (cheap check skipped)", + "eip1271 sim infra error (signature check skipped)", ), SimOutcome::Pass => {} } @@ -378,7 +385,7 @@ pub enum ValidationError { /// reverted or did not return the expected value. InvalidEip1271Signature(B256), /// The EIP-1271 order simulation returned a revert in enforce mode. Only - /// possible when the cheap 1271 signature check passed but the full + /// possible when the 1271 signature check passed but the full /// order simulation failed. The Tenderly URL, when available, is /// logged separately and is intentionally not surfaced here. SimulationFailed(String), @@ -590,12 +597,12 @@ impl OrderValidator { return Ok(0u64); } - let cheap_fut = self + let signature_fut = self .signature_validator .validate_signature_and_get_additional_gas(check); let Some(config) = &self.eip1271_sim else { - return cheap_fut.await.map_err(|err| match err { + return signature_fut.await.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), SignatureValidationError::Other(err) => ValidationError::Other(err), }); @@ -611,19 +618,19 @@ impl OrderValidator { }; let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); - let (cheap_res, sim_res) = tokio::join!(cheap_fut, sim_fut); + let (signature_res, sim_res) = tokio::join!(signature_fut, sim_fut); drop(timer); - let cheap_outcome = classify_cheap(&cheap_res); + let signature_outcome = classify_signature(&signature_res); let sim_outcome = classify_sim(&sim_res); record_sim_outcome( - cheap_outcome, + signature_outcome, &sim_outcome, preview_order.metadata.uid, preview_order.metadata.owner, ); - match (cheap_res, &sim_outcome, config.mode) { + match (signature_res, &sim_outcome, config.mode) { (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271SimMode::Enforce) => { Err(ValidationError::SimulationFailed(reason.clone())) } @@ -2974,7 +2981,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_pass_cheap_pass_accepts() { + async fn shadow_mode_sim_pass_sig_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -2995,7 +3002,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_fail_cheap_pass_accepts() { + async fn shadow_mode_sim_fail_sig_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3021,7 +3028,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_pass_cheap_fail_rejects_with_invalid_sig() { + async fn shadow_mode_sim_pass_sig_fail_rejects_with_invalid_sig() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3046,7 +3053,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_fail_cheap_fail_rejects_with_invalid_sig() { + async fn shadow_mode_sim_fail_sig_fail_rejects_with_invalid_sig() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3076,7 +3083,7 @@ mod tests { } #[tokio::test] - async fn enforce_mode_cheap_pass_sim_fail_rejects_with_simulation_failed() { + async fn enforce_mode_sig_pass_sim_fail_rejects_with_simulation_failed() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3108,7 +3115,7 @@ mod tests { } #[tokio::test] - async fn enforce_mode_cheap_fail_sim_fail_returns_invalid_sig() { + async fn enforce_mode_sig_fail_sim_fail_returns_invalid_sig() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3138,7 +3145,7 @@ mod tests { } #[tokio::test] - async fn enforce_mode_cheap_pass_sim_pass_accepts() { + async fn enforce_mode_sig_pass_sim_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() From cebd99b9d11240dc90d7843c8e662dda70fd9c3a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:20:21 +0100 Subject: [PATCH 017/154] Rename duration_seconds histogram to simulation_time --- crates/shared/src/order_validation.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 097992b09c..721f13ee36 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -107,8 +107,8 @@ struct Eip1271SimMetrics { /// `pass | fail | infra`. #[metric(labels("signature", "sim"))] total: prometheus::IntCounterVec, - /// Duration of the EIP-1271 order simulation. - duration_seconds: prometheus::Histogram, + /// Time spent in the EIP-1271 order simulation. + simulation_time: prometheus::Histogram, } impl Eip1271SimMetrics { @@ -220,7 +220,7 @@ fn record_sim_outcome( } async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) { - let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); + let timer = Eip1271SimMetrics::get().simulation_time.start_timer(); let res = tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await; drop(timer); let outcome = match res { @@ -617,7 +617,7 @@ impl OrderValidator { .unwrap_or_else(|_| Err(Eip1271SimError::Infra(anyhow!("eip1271 sim timeout")))) }; - let timer = Eip1271SimMetrics::get().duration_seconds.start_timer(); + let timer = Eip1271SimMetrics::get().simulation_time.start_timer(); let (signature_res, sim_res) = tokio::join!(signature_fut, sim_fut); drop(timer); From 67a4d3252095197203f23b1d6341d41eab445878 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:26:55 +0100 Subject: [PATCH 018/154] Drop owner from sim logs; OrderUid already encodes it --- crates/shared/src/order_validation.rs | 39 ++++++++++----------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 721f13ee36..f8b112c9aa 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -181,19 +181,14 @@ fn classify_sim(res: &Result<(), Eip1271SimError>) -> SimOutcome { } } -fn record_sim_outcome( - cheap: SignatureOutcome, - sim: &SimOutcome, - order_uid: OrderUid, - owner: Address, -) { +fn record_sim_outcome(signature: SignatureOutcome, sim: &SimOutcome, order_uid: OrderUid) { Eip1271SimMetrics::get() .total - .with_label_values(&[cheap.label(), sim.label()]) + .with_label_values(&[signature.label(), sim.label()]) .inc(); let disagreement = matches!( - (&cheap, sim), + (&signature, sim), (SignatureOutcome::Pass, SimOutcome::Fail { .. }) | (SignatureOutcome::Fail, SimOutcome::Pass) ); @@ -207,22 +202,22 @@ fn record_sim_outcome( }; tracing::warn!( %order_uid, - %owner, - cheap = cheap.label(), + signature = signature.label(), sim = sim.label(), reason, ?tenderly_url, "eip1271 sim disagreement", ); } else if let SimOutcome::Infra(err) = sim { - tracing::warn!(%order_uid, %owner, err = %err, "eip1271 sim infra error"); + tracing::warn!(%order_uid, err = %err, "eip1271 sim infra error"); } } async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) { - let timer = Eip1271SimMetrics::get().simulation_time.start_timer(); - let res = tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await; - drop(timer); + let res = { + let _timer = Eip1271SimMetrics::get().simulation_time.start_timer(); + tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await + }; let outcome = match res { Ok(Ok(())) => SimOutcome::Pass, Ok(Err(Eip1271SimError::Reverted { @@ -245,14 +240,12 @@ async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) tenderly_url, } => tracing::info!( order_uid = %preview_order.metadata.uid, - owner = %preview_order.metadata.owner, reason = %reason, ?tenderly_url, "eip1271 sim (signature check skipped)", ), SimOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, - owner = %preview_order.metadata.owner, err = %err, "eip1271 sim infra error (signature check skipped)", ), @@ -617,18 +610,14 @@ impl OrderValidator { .unwrap_or_else(|_| Err(Eip1271SimError::Infra(anyhow!("eip1271 sim timeout")))) }; - let timer = Eip1271SimMetrics::get().simulation_time.start_timer(); - let (signature_res, sim_res) = tokio::join!(signature_fut, sim_fut); - drop(timer); + let (signature_res, sim_res) = { + let _timer = Eip1271SimMetrics::get().simulation_time.start_timer(); + tokio::join!(signature_fut, sim_fut) + }; let signature_outcome = classify_signature(&signature_res); let sim_outcome = classify_sim(&sim_res); - record_sim_outcome( - signature_outcome, - &sim_outcome, - preview_order.metadata.uid, - preview_order.metadata.owner, - ); + record_sim_outcome(signature_outcome, &sim_outcome, preview_order.metadata.uid); match (signature_res, &sim_outcome, config.mode) { (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271SimMode::Enforce) => { From 2fbf9f0667dc9f60c439534890133d6d62d2ce00 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:29:35 +0100 Subject: [PATCH 019/154] Split sim disagreement log into explicit arms instead of an empty-string fallback --- crates/shared/src/order_validation.rs | 36 ++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index f8b112c9aa..56d503047f 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -187,29 +187,31 @@ fn record_sim_outcome(signature: SignatureOutcome, sim: &SimOutcome, order_uid: .with_label_values(&[signature.label(), sim.label()]) .inc(); - let disagreement = matches!( - (&signature, sim), - (SignatureOutcome::Pass, SimOutcome::Fail { .. }) - | (SignatureOutcome::Fail, SimOutcome::Pass) - ); - if disagreement { - let (reason, tenderly_url) = match sim { + match (signature, sim) { + ( + SignatureOutcome::Pass, SimOutcome::Fail { reason, tenderly_url, - } => (reason.as_str(), tenderly_url.as_deref()), - _ => ("", None), - }; - tracing::warn!( + }, + ) => tracing::warn!( %order_uid, - signature = signature.label(), - sim = sim.label(), - reason, + signature = "pass", + sim = "fail", + reason = %reason, ?tenderly_url, "eip1271 sim disagreement", - ); - } else if let SimOutcome::Infra(err) = sim { - tracing::warn!(%order_uid, err = %err, "eip1271 sim infra error"); + ), + (SignatureOutcome::Fail, SimOutcome::Pass) => tracing::warn!( + %order_uid, + signature = "fail", + sim = "pass", + "eip1271 sim disagreement", + ), + (_, SimOutcome::Infra(err)) => { + tracing::warn!(%order_uid, err = %err, "eip1271 sim infra error") + } + _ => {} } } From a781c443932ab8d8a1ce97cee8db985cbe231dd9 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:31:16 +0100 Subject: [PATCH 020/154] Use Debug formatting (?) in sim logs to match project convention --- crates/shared/src/order_validation.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 56d503047f..29c9b5ed64 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -195,21 +195,21 @@ fn record_sim_outcome(signature: SignatureOutcome, sim: &SimOutcome, order_uid: tenderly_url, }, ) => tracing::warn!( - %order_uid, + ?order_uid, signature = "pass", sim = "fail", - reason = %reason, + ?reason, ?tenderly_url, "eip1271 sim disagreement", ), (SignatureOutcome::Fail, SimOutcome::Pass) => tracing::warn!( - %order_uid, + ?order_uid, signature = "fail", sim = "pass", "eip1271 sim disagreement", ), (_, SimOutcome::Infra(err)) => { - tracing::warn!(%order_uid, err = %err, "eip1271 sim infra error") + tracing::warn!(?order_uid, ?err, "eip1271 sim infra error") } _ => {} } @@ -242,13 +242,13 @@ async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) tenderly_url, } => tracing::info!( order_uid = %preview_order.metadata.uid, - reason = %reason, + ?reason, ?tenderly_url, "eip1271 sim (signature check skipped)", ), SimOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, - err = %err, + ?err, "eip1271 sim infra error (signature check skipped)", ), SimOutcome::Pass => {} From d11442e0b453cf77210a920ee81f23c9166e34c2 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:39:25 +0100 Subject: [PATCH 021/154] Expand sim abbreviation to simulation across types, fields, metrics, 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. --- crates/configs/src/orderbook/mod.rs | 35 +-- .../{eip1271_sim.rs => eip1271_simulation.rs} | 10 +- crates/orderbook/src/lib.rs | 2 +- crates/orderbook/src/run.rs | 18 +- crates/shared/src/order_validation.rs | 220 ++++++++++-------- 5 files changed, 159 insertions(+), 126 deletions(-) rename crates/orderbook/src/{eip1271_sim.rs => eip1271_simulation.rs} (84%) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 3197e57151..a4477fe2e2 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -63,24 +63,27 @@ pub struct OrderSimulationConfig { /// Mode for the EIP-1271 order simulation. #[serde(default)] - pub eip1271_sim_mode: Eip1271SimMode, + pub eip1271_simulation_mode: Eip1271SimulationMode, /// Per-call timeout for the EIP-1271 order simulation. - #[serde(default = "default_eip1271_sim_timeout", with = "humantime_serde")] - pub eip1271_sim_timeout: Duration, + #[serde( + default = "default_eip1271_simulation_timeout", + with = "humantime_serde" + )] + pub eip1271_simulation_timeout: Duration, } /// Mode for the EIP-1271 order simulation. Mirrored by -/// `shared::order_validation::Eip1271SimMode`. +/// `shared::order_validation::Eip1271SimulationMode`. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] -pub enum Eip1271SimMode { +pub enum Eip1271SimulationMode { #[default] Shadow, Enforce, } -fn default_eip1271_sim_timeout() -> Duration { +fn default_eip1271_simulation_timeout() -> Duration { Duration::from_secs(2) } @@ -251,8 +254,8 @@ pub mod test_util { order_simulation: Some(OrderSimulationConfig { gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), tenderly: None, - eip1271_sim_mode: Default::default(), - eip1271_sim_timeout: std::time::Duration::from_secs(2), + eip1271_simulation_mode: Default::default(), + eip1271_simulation_timeout: std::time::Duration::from_secs(2), }), hide_competition_before_deadline: false, } @@ -465,24 +468,24 @@ mod tests { } #[test] - fn parses_sim_mode_default() { + fn parses_simulation_mode_default() { let toml = r#" gas-limit = "0x1000000" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_sim_mode, Eip1271SimMode::Shadow); - assert_eq!(cfg.eip1271_sim_timeout, Duration::from_secs(2)); + assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Shadow); + assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(2)); } #[test] - fn parses_sim_mode_enforce() { + fn parses_simulation_mode_enforce() { let toml = r#" gas-limit = "0x1000000" -eip1271-sim-mode = "enforce" -eip1271-sim-timeout = "5s" +eip1271-simulation-mode = "enforce" +eip1271-simulation-timeout = "5s" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_sim_mode, Eip1271SimMode::Enforce); - assert_eq!(cfg.eip1271_sim_timeout, Duration::from_secs(5)); + assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Enforce); + assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(5)); } } diff --git a/crates/orderbook/src/eip1271_sim.rs b/crates/orderbook/src/eip1271_simulation.rs similarity index 84% rename from crates/orderbook/src/eip1271_sim.rs rename to crates/orderbook/src/eip1271_simulation.rs index c58a5409d2..026881f65b 100644 --- a/crates/orderbook/src/eip1271_sim.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -2,7 +2,7 @@ use { crate::order_simulator::{self, OrderSimulator}, async_trait::async_trait, model::order::Order, - shared::order_validation::{Eip1271SimError, Eip1271Simulating}, + shared::order_validation::{Eip1271Simulating, Eip1271SimulationError}, std::sync::Arc, }; @@ -24,7 +24,7 @@ impl OrderSimulatorAdapter { #[async_trait] impl Eip1271Simulating for OrderSimulatorAdapter { - async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError> { + async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimulationError> { let swap = self .inner .encode_order(order, Vec::new(), None) @@ -37,7 +37,7 @@ impl Eip1271Simulating for OrderSimulatorAdapter { .map_err(map_simulator_err)?; match result.error { None => Ok(()), - Some(reason) => Err(Eip1271SimError::Reverted { + Some(reason) => Err(Eip1271SimulationError::Reverted { reason, tenderly_url: result.tenderly_url, }), @@ -45,10 +45,10 @@ impl Eip1271Simulating for OrderSimulatorAdapter { } } -fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimError { +fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimulationError { match err { order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => { - Eip1271SimError::Infra(e) + Eip1271SimulationError::Infra(e) } } } diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index 267135811d..98bce27e05 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -3,7 +3,7 @@ pub mod app_data; pub mod arguments; pub mod database; pub mod dto; -mod eip1271_sim; +mod eip1271_simulation; mod ipfs; mod ipfs_app_data; pub mod order_simulator; diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 4ee95ec26e..4e5ddb95e7 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -42,7 +42,7 @@ use { shared::{ order_quoting::{self, OrderQuoter}, order_validation::{ - Eip1271SimMode, + Eip1271SimulationMode, Eip1271Simulator, OrderValidPeriodConfiguration, OrderValidator, @@ -381,7 +381,7 @@ pub async fn run(config: Configuration) { .await .ok(); - let (order_simulator, eip1271_sim) = match config.order_simulation { + let (order_simulator, eip1271_simulator) = match config.order_simulation { Some(sim_config) => { let tenderly: Option> = sim_config.tenderly.as_ref().map(|tenderly_config| { @@ -409,18 +409,20 @@ pub async fn run(config: Configuration) { tenderly, )); let simulator: Arc = Arc::new( - crate::eip1271_sim::OrderSimulatorAdapter::new(order_simulator.clone()), + crate::eip1271_simulation::OrderSimulatorAdapter::new(order_simulator.clone()), ); - let mode = match sim_config.eip1271_sim_mode { - configs::orderbook::Eip1271SimMode::Shadow => Eip1271SimMode::Shadow, - configs::orderbook::Eip1271SimMode::Enforce => Eip1271SimMode::Enforce, + let mode = match sim_config.eip1271_simulation_mode { + configs::orderbook::Eip1271SimulationMode::Shadow => Eip1271SimulationMode::Shadow, + configs::orderbook::Eip1271SimulationMode::Enforce => { + Eip1271SimulationMode::Enforce + } }; ( Some(order_simulator), Some(Eip1271Simulator { simulator, mode, - timeout: sim_config.eip1271_sim_timeout, + timeout: sim_config.eip1271_simulation_timeout, }), ) } @@ -441,7 +443,7 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, - eip1271_sim, + eip1271_simulator, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 29c9b5ed64..3788581037 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -50,7 +50,7 @@ use { /// Outcome of the EIP-1271 order simulation. #[derive(Debug)] -pub enum Eip1271SimError { +pub enum Eip1271SimulationError { /// The simulation ran and the transaction reverted. `reason` is the /// revert string returned by the EVM (or a Tenderly reason string). Reverted { @@ -65,7 +65,7 @@ pub enum Eip1271SimError { /// Optional hook used by `OrderValidator` to run a full order simulation /// next to the cheap EIP-1271 signature check. A concrete implementation -/// lives in `crates/orderbook/src/eip1271_sim.rs`. +/// lives in `crates/orderbook/src/eip1271_simulation.rs`. /// /// The trait exists in `shared` because `OrderValidator` lives in `shared` /// and cannot depend upward on `orderbook`. It is designed to be replaced @@ -74,12 +74,12 @@ pub enum Eip1271SimError { #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait Eip1271Simulating: Send + Sync { - async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimError>; + async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimulationError>; } /// Mode controlling whether the EIP-1271 order simulation can reject orders. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub enum Eip1271SimMode { +pub enum Eip1271SimulationMode { /// Log disagreements, emit metrics. Never reject. **Default.** #[default] Shadow, @@ -95,26 +95,26 @@ pub enum Eip1271SimMode { #[derive(Clone)] pub struct Eip1271Simulator { pub simulator: Arc, - pub mode: Eip1271SimMode, + pub mode: Eip1271SimulationMode, pub timeout: Duration, } #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] -#[metric(subsystem = "eip1271_sim")] -struct Eip1271SimMetrics { - /// Counts each (signature-check, sim) outcome pair. The signature - /// axis takes `pass | fail | infra | skipped`; the sim axis takes - /// `pass | fail | infra`. - #[metric(labels("signature", "sim"))] +#[metric(subsystem = "eip1271_simulation")] +struct Eip1271SimulationMetrics { + /// Counts each (signature-check, simulation) outcome pair. The signature + /// axis takes `pass | fail | infra | skipped`; the simulation axis + /// takes `pass | fail | infra`. + #[metric(labels("signature", "simulation"))] total: prometheus::IntCounterVec, /// Time spent in the EIP-1271 order simulation. simulation_time: prometheus::Histogram, } -impl Eip1271SimMetrics { +impl Eip1271SimulationMetrics { fn get() -> &'static Self { Self::instance(observe::metrics::get_storage_registry()) - .expect("unexpected error getting Eip1271SimMetrics instance") + .expect("unexpected error getting Eip1271SimulationMetrics instance") } } @@ -129,7 +129,7 @@ enum SignatureOutcome { } #[derive(Debug)] -enum SimOutcome { +enum SimulationOutcome { Pass, Fail { reason: String, @@ -149,7 +149,7 @@ impl SignatureOutcome { } } -impl SimOutcome { +impl SimulationOutcome { fn label(&self) -> &'static str { match self { Self::Pass => "pass", @@ -167,91 +167,97 @@ fn classify_signature(res: &Result) -> SignatureO } } -fn classify_sim(res: &Result<(), Eip1271SimError>) -> SimOutcome { +fn classify_simulation(res: &Result<(), Eip1271SimulationError>) -> SimulationOutcome { match res { - Ok(()) => SimOutcome::Pass, - Err(Eip1271SimError::Reverted { + Ok(()) => SimulationOutcome::Pass, + Err(Eip1271SimulationError::Reverted { reason, tenderly_url, - }) => SimOutcome::Fail { + }) => SimulationOutcome::Fail { reason: reason.clone(), tenderly_url: tenderly_url.clone(), }, - Err(Eip1271SimError::Infra(err)) => SimOutcome::Infra(anyhow!("{err}")), + Err(Eip1271SimulationError::Infra(err)) => SimulationOutcome::Infra(anyhow!("{err}")), } } -fn record_sim_outcome(signature: SignatureOutcome, sim: &SimOutcome, order_uid: OrderUid) { - Eip1271SimMetrics::get() +fn record_simulation_outcome( + signature: SignatureOutcome, + simulation: &SimulationOutcome, + order_uid: OrderUid, +) { + Eip1271SimulationMetrics::get() .total - .with_label_values(&[signature.label(), sim.label()]) + .with_label_values(&[signature.label(), simulation.label()]) .inc(); - match (signature, sim) { + match (signature, simulation) { ( SignatureOutcome::Pass, - SimOutcome::Fail { + SimulationOutcome::Fail { reason, tenderly_url, }, ) => tracing::warn!( ?order_uid, signature = "pass", - sim = "fail", + simulation = "fail", ?reason, ?tenderly_url, - "eip1271 sim disagreement", + "eip1271 simulation disagreement", ), - (SignatureOutcome::Fail, SimOutcome::Pass) => tracing::warn!( + (SignatureOutcome::Fail, SimulationOutcome::Pass) => tracing::warn!( ?order_uid, signature = "fail", - sim = "pass", - "eip1271 sim disagreement", + simulation = "pass", + "eip1271 simulation disagreement", ), - (_, SimOutcome::Infra(err)) => { - tracing::warn!(?order_uid, ?err, "eip1271 sim infra error") + (_, SimulationOutcome::Infra(err)) => { + tracing::warn!(?order_uid, ?err, "eip1271 simulation infra error") } _ => {} } } -async fn run_eip1271_sim_only(config: &Eip1271Simulator, preview_order: &Order) { +async fn run_eip1271_simulation_only(config: &Eip1271Simulator, preview_order: &Order) { let res = { - let _timer = Eip1271SimMetrics::get().simulation_time.start_timer(); + let _timer = Eip1271SimulationMetrics::get() + .simulation_time + .start_timer(); tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await }; let outcome = match res { - Ok(Ok(())) => SimOutcome::Pass, - Ok(Err(Eip1271SimError::Reverted { + Ok(Ok(())) => SimulationOutcome::Pass, + Ok(Err(Eip1271SimulationError::Reverted { reason, tenderly_url, - })) => SimOutcome::Fail { + })) => SimulationOutcome::Fail { reason, tenderly_url, }, - Ok(Err(Eip1271SimError::Infra(err))) => SimOutcome::Infra(err), - Err(_) => SimOutcome::Infra(anyhow!("eip1271 sim timeout")), + Ok(Err(Eip1271SimulationError::Infra(err))) => SimulationOutcome::Infra(err), + Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), }; - Eip1271SimMetrics::get() + Eip1271SimulationMetrics::get() .total .with_label_values(&[SignatureOutcome::Skipped.label(), outcome.label()]) .inc(); match &outcome { - SimOutcome::Fail { + SimulationOutcome::Fail { reason, tenderly_url, } => tracing::info!( order_uid = %preview_order.metadata.uid, ?reason, ?tenderly_url, - "eip1271 sim (signature check skipped)", + "eip1271 simulation (signature check skipped)", ), - SimOutcome::Infra(err) => tracing::warn!( + SimulationOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, ?err, - "eip1271 sim infra error (signature check skipped)", + "eip1271 simulation infra error (signature check skipped)", ), - SimOutcome::Pass => {} + SimulationOutcome::Pass => {} } } @@ -480,7 +486,7 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_sim: Option, + eip1271_simulator: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -551,7 +557,7 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_sim: Option, + eip1271_simulator: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -569,7 +575,7 @@ impl OrderValidator { quoter, balance_fetcher, signature_validator, - eip1271_sim, + eip1271_simulator, limit_order_counter, max_limit_orders_per_user, code_fetcher, @@ -586,8 +592,8 @@ impl OrderValidator { hash: B256, ) -> Result { if self.eip1271_skip_creation_validation { - if let Some(config) = &self.eip1271_sim { - run_eip1271_sim_only(config, preview_order).await; + if let Some(config) = &self.eip1271_simulator { + run_eip1271_simulation_only(config, preview_order).await; } return Ok(0u64); } @@ -596,7 +602,7 @@ impl OrderValidator { .signature_validator .validate_signature_and_get_additional_gas(check); - let Some(config) = &self.eip1271_sim else { + let Some(config) = &self.eip1271_simulator else { return signature_fut.await.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), SignatureValidationError::Other(err) => ValidationError::Other(err), @@ -604,25 +610,35 @@ impl OrderValidator { }; let sim = config.simulator.clone(); - let sim_timeout = config.timeout; + let simulation_timeout = config.timeout; let order = preview_order.clone(); - let sim_fut = async move { - tokio::time::timeout(sim_timeout, sim.simulate(&order)) + let simulation_fut = async move { + tokio::time::timeout(simulation_timeout, sim.simulate(&order)) .await - .unwrap_or_else(|_| Err(Eip1271SimError::Infra(anyhow!("eip1271 sim timeout")))) + .unwrap_or_else(|_| { + Err(Eip1271SimulationError::Infra(anyhow!( + "eip1271 simulation timeout" + ))) + }) }; - let (signature_res, sim_res) = { - let _timer = Eip1271SimMetrics::get().simulation_time.start_timer(); - tokio::join!(signature_fut, sim_fut) + let (signature_res, simulation_res) = { + let _timer = Eip1271SimulationMetrics::get() + .simulation_time + .start_timer(); + tokio::join!(signature_fut, simulation_fut) }; let signature_outcome = classify_signature(&signature_res); - let sim_outcome = classify_sim(&sim_res); - record_sim_outcome(signature_outcome, &sim_outcome, preview_order.metadata.uid); + let simulation_outcome = classify_simulation(&simulation_res); + record_simulation_outcome( + signature_outcome, + &simulation_outcome, + preview_order.metadata.uid, + ); - match (signature_res, &sim_outcome, config.mode) { - (Ok(_gas), SimOutcome::Fail { reason, .. }, Eip1271SimMode::Enforce) => { + match (signature_res, &simulation_outcome, config.mode) { + (Ok(_gas), SimulationOutcome::Fail { reason, .. }, Eip1271SimulationMode::Enforce) => { Err(ValidationError::SimulationFailed(reason.clone())) } (Ok(gas), _, _) => Ok(gas), @@ -2914,25 +2930,25 @@ mod tests { } } - fn shadow_mode_sim(sim: MockEip1271Simulating) -> Eip1271Simulator { + fn shadow_mode_simulator(sim: MockEip1271Simulating) -> Eip1271Simulator { Eip1271Simulator { simulator: Arc::new(sim), - mode: Eip1271SimMode::Shadow, + mode: Eip1271SimulationMode::Shadow, timeout: DEFAULT_EIP1271_SIM_TIMEOUT, } } - fn enforce_mode_sim(sim: MockEip1271Simulating) -> Eip1271Simulator { + fn enforce_mode_simulator(sim: MockEip1271Simulating) -> Eip1271Simulator { Eip1271Simulator { simulator: Arc::new(sim), - mode: Eip1271SimMode::Enforce, + mode: Eip1271SimulationMode::Enforce, timeout: DEFAULT_EIP1271_SIM_TIMEOUT, } } fn build_1271_validator( signature_validator: MockSignatureValidating, - eip1271_sim: Option, + eip1271_simulator: Option, eip1271_skip_creation_validation: bool, ) -> OrderValidator { let mut order_quoter = MockOrderQuoting::new(); @@ -2961,7 +2977,7 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validator), - eip1271_sim, + eip1271_simulator, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -2972,7 +2988,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_pass_sig_pass_accepts() { + async fn shadow_mode_simulation_pass_signature_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -2980,7 +2996,7 @@ mod tests { let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); let validator = - build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); + build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -2993,20 +3009,20 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_fail_sig_pass_accepts() { + async fn shadow_mode_simulation_fail_signature_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { - Err(Eip1271SimError::Reverted { + Err(Eip1271SimulationError::Reverted { reason: "hook revert".into(), tenderly_url: None, }) }); let validator = - build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); + build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3019,7 +3035,7 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_pass_sig_fail_rejects_with_invalid_sig() { + async fn shadow_mode_simulation_pass_signature_fail_rejects_with_invalid_signature() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3027,7 +3043,7 @@ mod tests { let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); let validator = - build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); + build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3044,20 +3060,20 @@ mod tests { } #[tokio::test] - async fn shadow_mode_sim_fail_sig_fail_rejects_with_invalid_sig() { + async fn shadow_mode_simulation_fail_signature_fail_rejects_with_invalid_signature() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { - Err(Eip1271SimError::Reverted { + Err(Eip1271SimulationError::Reverted { reason: "x".into(), tenderly_url: None, }) }); let validator = - build_1271_validator(signature_validator, Some(shadow_mode_sim(sim)), false); + build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3074,20 +3090,23 @@ mod tests { } #[tokio::test] - async fn enforce_mode_sig_pass_sim_fail_rejects_with_simulation_failed() { + async fn enforce_mode_signature_pass_simulation_fail_rejects_with_simulation_failed() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { - Err(Eip1271SimError::Reverted { + Err(Eip1271SimulationError::Reverted { reason: "hook reverted: INSUFFICIENT_OUT".into(), tenderly_url: None, }) }); - let validator = - build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); + let validator = build_1271_validator( + signature_validator, + Some(enforce_mode_simulator(sim)), + false, + ); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3106,20 +3125,23 @@ mod tests { } #[tokio::test] - async fn enforce_mode_sig_fail_sim_fail_returns_invalid_sig() { + async fn enforce_mode_signature_fail_simulation_fail_returns_invalid_signature() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Err(SignatureValidationError::Invalid)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { - Err(Eip1271SimError::Reverted { + Err(Eip1271SimulationError::Reverted { reason: "x".into(), tenderly_url: None, }) }); - let validator = - build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); + let validator = build_1271_validator( + signature_validator, + Some(enforce_mode_simulator(sim)), + false, + ); let err = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3136,15 +3158,18 @@ mod tests { } #[tokio::test] - async fn enforce_mode_sig_pass_sim_pass_accepts() { + async fn enforce_mode_signature_pass_simulation_pass_accepts() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| Ok(())); - let validator = - build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), false); + let validator = build_1271_validator( + signature_validator, + Some(enforce_mode_simulator(sim)), + false, + ); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3157,15 +3182,18 @@ mod tests { } #[tokio::test] - async fn sim_infra_error_is_fail_open_in_both_modes() { - for mode in [Eip1271SimMode::Shadow, Eip1271SimMode::Enforce] { + async fn simulation_infra_error_is_fail_open_in_both_modes() { + for mode in [ + Eip1271SimulationMode::Shadow, + Eip1271SimulationMode::Enforce, + ] { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate() - .returning(|_| Err(Eip1271SimError::Infra(anyhow!("RPC down")))); + .returning(|_| Err(Eip1271SimulationError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( signature_validator, Some(Eip1271Simulator { @@ -3191,20 +3219,20 @@ mod tests { } #[tokio::test] - async fn skip_flag_runs_sim_only_and_never_rejects() { + async fn skip_flag_runs_simulation_only_and_never_rejects() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); let mut sim = MockEip1271Simulating::new(); sim.expect_simulate().returning(|_| { - Err(Eip1271SimError::Reverted { + Err(Eip1271SimulationError::Reverted { reason: "x".into(), tenderly_url: None, }) }); let validator = - build_1271_validator(signature_validator, Some(enforce_mode_sim(sim)), true); + build_1271_validator(signature_validator, Some(enforce_mode_simulator(sim)), true); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3217,7 +3245,7 @@ mod tests { } #[tokio::test] - async fn no_sim_configured_preserves_existing_behaviour() { + async fn no_simulator_configured_preserves_existing_behaviour() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() From eca5c61144272f10e62069e6909dc8d310fad6ae Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:46:44 +0100 Subject: [PATCH 022/154] Simplify Eip1271Simulating doc and scope its mock to cfg(test) only --- crates/shared/src/order_validation.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 3788581037..b7610b247a 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -63,15 +63,13 @@ pub enum Eip1271SimulationError { Infra(anyhow::Error), } -/// Optional hook used by `OrderValidator` to run a full order simulation -/// next to the cheap EIP-1271 signature check. A concrete implementation -/// lives in `crates/orderbook/src/eip1271_simulation.rs`. +/// Simulates an EIP-1271 order end-to-end (pre-hooks → swap → post-hooks). /// -/// The trait exists in `shared` because `OrderValidator` lives in `shared` -/// and cannot depend upward on `orderbook`. It is designed to be replaced -/// by a direct `simulator::OrderSimulator` dependency once the simulator -/// crate is refactored. -#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] +/// Defined here rather than in `crates/simulator` because `OrderValidator` +/// cannot depend on `orderbook`, where the concrete implementation lives. +/// To be removed once the `simulator` crate's API can be depended on +/// directly. +#[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] pub trait Eip1271Simulating: Send + Sync { async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimulationError>; From 5407c8b171745b3b5ec5e37a041f45a5d7f346e3 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:53:00 +0100 Subject: [PATCH 023/154] Add Disabled mode for order-creation EIP-1271 simulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/configs/src/orderbook/mod.rs | 20 +++++++++++++++++-- crates/orderbook/src/eip1271_simulation.rs | 11 ----------- crates/orderbook/src/run.rs | 23 ++++++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index a4477fe2e2..49538ab824 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -73,14 +73,20 @@ pub struct OrderSimulationConfig { pub eip1271_simulation_timeout: Duration, } -/// Mode for the EIP-1271 order simulation. Mirrored by -/// `shared::order_validation::Eip1271SimulationMode`. +/// Mode for the EIP-1271 order simulation at order creation. +/// +/// `Disabled` turns the simulation off in the order-creation path entirely +/// (useful for chains where the extra latency isn't worth it, or to +/// troubleshoot false rejections without dropping the debug endpoint). +/// The debug simulation endpoint continues to work regardless of this +/// setting as long as `order_simulation` itself is configured. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Eip1271SimulationMode { #[default] Shadow, Enforce, + Disabled, } fn default_eip1271_simulation_timeout() -> Duration { @@ -488,4 +494,14 @@ eip1271-simulation-timeout = "5s" assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Enforce); assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(5)); } + + #[test] + fn parses_simulation_mode_disabled() { + let toml = r#" +gas-limit = "0x1000000" +eip1271-simulation-mode = "disabled" +"#; + let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); + assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); + } } diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index 026881f65b..9bd97ec918 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -52,14 +52,3 @@ fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimulationError { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn impls_trait() { - fn assert_impl() {} - assert_impl::(); - } -} diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 4e5ddb95e7..24d548562e 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -408,23 +408,26 @@ pub async fn run(config: Configuration) { chain.id().to_string(), tenderly, )); - let simulator: Arc = Arc::new( - crate::eip1271_simulation::OrderSimulatorAdapter::new(order_simulator.clone()), - ); let mode = match sim_config.eip1271_simulation_mode { - configs::orderbook::Eip1271SimulationMode::Shadow => Eip1271SimulationMode::Shadow, + configs::orderbook::Eip1271SimulationMode::Shadow => { + Some(Eip1271SimulationMode::Shadow) + } configs::orderbook::Eip1271SimulationMode::Enforce => { - Eip1271SimulationMode::Enforce + Some(Eip1271SimulationMode::Enforce) } + configs::orderbook::Eip1271SimulationMode::Disabled => None, }; - ( - Some(order_simulator), - Some(Eip1271Simulator { + let eip1271_simulator = mode.map(|mode| { + let simulator: Arc = Arc::new( + crate::eip1271_simulation::OrderSimulatorAdapter::new(order_simulator.clone()), + ); + Eip1271Simulator { simulator, mode, timeout: sim_config.eip1271_simulation_timeout, - }), - ) + } + }); + (Some(order_simulator), eip1271_simulator) } None => (None, None), }; From eed5990b238e9ab7dceb8d897b4b6aa4781c80c6 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:57:07 +0100 Subject: [PATCH 024/154] Disabled by default --- crates/configs/src/orderbook/mod.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 49538ab824..ebfab644ed 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -74,18 +74,12 @@ pub struct OrderSimulationConfig { } /// Mode for the EIP-1271 order simulation at order creation. -/// -/// `Disabled` turns the simulation off in the order-creation path entirely -/// (useful for chains where the extra latency isn't worth it, or to -/// troubleshoot false rejections without dropping the debug endpoint). -/// The debug simulation endpoint continues to work regardless of this -/// setting as long as `order_simulation` itself is configured. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Eip1271SimulationMode { - #[default] Shadow, Enforce, + #[default] Disabled, } From 2785108e68aebc7122a4c6f067ef98272cfa88b6 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 19:59:22 +0100 Subject: [PATCH 025/154] Align default test with Disabled as the default simulation mode --- crates/configs/src/orderbook/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index ebfab644ed..82f47adc8d 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -473,7 +473,7 @@ mod tests { gas-limit = "0x1000000" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Shadow); + assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(2)); } From 7281ded5efdfd4dd416b19a7a545269e451d14bb Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 20:09:54 +0100 Subject: [PATCH 026/154] Redundant comment --- crates/shared/src/order_validation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index b7610b247a..4b11ca20d5 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -385,8 +385,7 @@ pub enum ValidationError { InvalidEip1271Signature(B256), /// The EIP-1271 order simulation returned a revert in enforce mode. Only /// possible when the 1271 signature check passed but the full - /// order simulation failed. The Tenderly URL, when available, is - /// logged separately and is intentionally not surfaced here. + /// order simulation failed. SimulationFailed(String), ZeroAmount, IncompatibleSigningScheme, From 352568497d47d94554fabd333cd7fcb42d9d20ec Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 20:14:10 +0100 Subject: [PATCH 027/154] Assert simulator is never invoked for non-EIP-1271 orders 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. --- crates/shared/src/order_validation.rs | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 4b11ca20d5..b8601ffc81 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -3241,6 +3241,40 @@ mod tests { assert!(result.is_ok(), "got {result:?}"); } + #[tokio::test] + async fn simulator_is_not_invoked_for_non_eip1271_orders() { + // Build an EOA (Eip712) order and a simulator configured in enforce mode. + // The simulator should NOT be called — only EIP-1271 orders go through + // the simulation path. + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .times(0); + let mut sim = MockEip1271Simulating::new(); + sim.expect_simulate().times(0); + let validator = build_1271_validator( + signature_validator, + Some(enforce_mode_simulator(sim)), + false, + ); + + let eoa_order = OrderCreation { + signature: Signature::Eip712(EcdsaSignature::non_zero()), + ..make_1271_order_creation() + }; + // Ignore the final result (it will fail WrongOwner/etc. later in the + // pipeline — we only care that the sim was not invoked). + let _ = validator + .validate_and_construct_order( + eoa_order, + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + // `sim.expect_simulate().times(0)` asserts on drop. + } + #[tokio::test] async fn no_simulator_configured_preserves_existing_behaviour() { let mut signature_validator = MockSignatureValidating::new(); From 191d7ff70834232ed9fe18425e97fb7545fe9798 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 20:17:15 +0100 Subject: [PATCH 028/154] Consolidate EIP-1271 signature/simulation quadrant tests into a matrix 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. --- crates/shared/src/order_validation.rs | 298 +++++++++----------------- 1 file changed, 99 insertions(+), 199 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index b8601ffc81..320944029f 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -2927,18 +2927,13 @@ mod tests { } } - fn shadow_mode_simulator(sim: MockEip1271Simulating) -> Eip1271Simulator { + fn simulator_with_mode( + sim: MockEip1271Simulating, + mode: Eip1271SimulationMode, + ) -> Eip1271Simulator { Eip1271Simulator { simulator: Arc::new(sim), - mode: Eip1271SimulationMode::Shadow, - timeout: DEFAULT_EIP1271_SIM_TIMEOUT, - } - } - - fn enforce_mode_simulator(sim: MockEip1271Simulating) -> Eip1271Simulator { - Eip1271Simulator { - simulator: Arc::new(sim), - mode: Eip1271SimulationMode::Enforce, + mode, timeout: DEFAULT_EIP1271_SIM_TIMEOUT, } } @@ -2984,200 +2979,102 @@ mod tests { ) } + /// Verifies the full (signature × simulation × mode) outcome matrix. + /// + /// Only the `(signature Pass, simulation Fail, Enforce)` cell changes + /// behaviour relative to today. Every other cell must match the + /// existing signature-only behaviour. #[tokio::test] - async fn shadow_mode_simulation_pass_signature_pass_accepts() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| Ok(())); - let validator = - build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); - let result = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await; - assert!(result.is_ok(), "expected Ok, got {result:?}"); - } + async fn signature_and_simulation_outcome_matrix() { + use Eip1271SimulationMode::{Enforce, Shadow}; - #[tokio::test] - async fn shadow_mode_simulation_fail_signature_pass_accepts() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| { - Err(Eip1271SimulationError::Reverted { - reason: "hook revert".into(), - tenderly_url: None, - }) - }); - let validator = - build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); - let result = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await; - assert!(result.is_ok(), "expected Ok in shadow mode, got {result:?}"); - } - - #[tokio::test] - async fn shadow_mode_simulation_pass_signature_fail_rejects_with_invalid_signature() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| Ok(())); - let validator = - build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); - let err = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await - .unwrap_err(); - assert!( - matches!(err, ValidationError::InvalidEip1271Signature(_)), - "got {err:?}" - ); - } + #[derive(Copy, Clone, Debug)] + enum Sig { + Pass, + Invalid, + } + #[derive(Copy, Clone, Debug)] + enum Sim { + Pass, + Reverted, + } + #[derive(Copy, Clone, Debug)] + enum Expected { + Accepted, + InvalidSignature, + SimulationFailed, + } - #[tokio::test] - async fn shadow_mode_simulation_fail_signature_fail_rejects_with_invalid_signature() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| { - Err(Eip1271SimulationError::Reverted { - reason: "x".into(), - tenderly_url: None, - }) - }); - let validator = - build_1271_validator(signature_validator, Some(shadow_mode_simulator(sim)), false); - let err = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await - .unwrap_err(); - assert!( - matches!(err, ValidationError::InvalidEip1271Signature(_)), - "got {err:?}" - ); - } + let cases: &[(Sig, Sim, Eip1271SimulationMode, Expected)] = &[ + (Sig::Pass, Sim::Pass, Shadow, Expected::Accepted), + (Sig::Pass, Sim::Reverted, Shadow, Expected::Accepted), + (Sig::Invalid, Sim::Pass, Shadow, Expected::InvalidSignature), + ( + Sig::Invalid, + Sim::Reverted, + Shadow, + Expected::InvalidSignature, + ), + (Sig::Pass, Sim::Pass, Enforce, Expected::Accepted), + ( + Sig::Pass, + Sim::Reverted, + Enforce, + Expected::SimulationFailed, + ), + (Sig::Invalid, Sim::Pass, Enforce, Expected::InvalidSignature), + ( + Sig::Invalid, + Sim::Reverted, + Enforce, + Expected::InvalidSignature, + ), + ]; - #[tokio::test] - async fn enforce_mode_signature_pass_simulation_fail_rejects_with_simulation_failed() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| { - Err(Eip1271SimulationError::Reverted { - reason: "hook reverted: INSUFFICIENT_OUT".into(), - tenderly_url: None, - }) - }); - let validator = build_1271_validator( - signature_validator, - Some(enforce_mode_simulator(sim)), - false, - ); - let err = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await - .unwrap_err(); - match err { - ValidationError::SimulationFailed(reason) => { - assert!(reason.contains("INSUFFICIENT_OUT"), "reason was {reason}"); + for &(sig, simulation, mode, expected) in cases { + let label = format!("sig={sig:?} sim={simulation:?} mode={mode:?}"); + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(move |_| match sig { + Sig::Pass => Ok(0u64), + Sig::Invalid => Err(SignatureValidationError::Invalid), + }); + let mut sim = MockEip1271Simulating::new(); + sim.expect_simulate().returning(move |_| match simulation { + Sim::Pass => Ok(()), + Sim::Reverted => Err(Eip1271SimulationError::Reverted { + reason: "hook reverted".into(), + tenderly_url: None, + }), + }); + let validator = build_1271_validator( + signature_validator, + Some(simulator_with_mode(sim, mode)), + false, + ); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + match expected { + Expected::Accepted => assert!(result.is_ok(), "{label}: got {result:?}"), + Expected::InvalidSignature => assert!( + matches!(result, Err(ValidationError::InvalidEip1271Signature(_))), + "{label}: got {result:?}" + ), + Expected::SimulationFailed => assert!( + matches!(result, Err(ValidationError::SimulationFailed(_))), + "{label}: got {result:?}" + ), } - other => panic!("expected SimulationFailed, got {other:?}"), } } - #[tokio::test] - async fn enforce_mode_signature_fail_simulation_fail_returns_invalid_signature() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Err(SignatureValidationError::Invalid)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| { - Err(Eip1271SimulationError::Reverted { - reason: "x".into(), - tenderly_url: None, - }) - }); - let validator = build_1271_validator( - signature_validator, - Some(enforce_mode_simulator(sim)), - false, - ); - let err = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await - .unwrap_err(); - assert!( - matches!(err, ValidationError::InvalidEip1271Signature(_)), - "got {err:?}" - ); - } - - #[tokio::test] - async fn enforce_mode_signature_pass_simulation_pass_accepts() { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulating::new(); - sim.expect_simulate().returning(|_| Ok(())); - let validator = build_1271_validator( - signature_validator, - Some(enforce_mode_simulator(sim)), - false, - ); - let result = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await; - assert!(result.is_ok(), "got {result:?}"); - } - #[tokio::test] async fn simulation_infra_error_is_fail_open_in_both_modes() { for mode in [ @@ -3228,8 +3125,11 @@ mod tests { tenderly_url: None, }) }); - let validator = - build_1271_validator(signature_validator, Some(enforce_mode_simulator(sim)), true); + let validator = build_1271_validator( + signature_validator, + Some(simulator_with_mode(sim, Eip1271SimulationMode::Enforce)), + true, + ); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3254,7 +3154,7 @@ mod tests { sim.expect_simulate().times(0); let validator = build_1271_validator( signature_validator, - Some(enforce_mode_simulator(sim)), + Some(simulator_with_mode(sim, Eip1271SimulationMode::Enforce)), false, ); From e62217c1d2197089db0e7491368a7e9ea20fbd8c Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 20:24:31 +0100 Subject: [PATCH 029/154] Use .label() on outcomes instead of hardcoded strings in sim logs --- crates/shared/src/order_validation.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 320944029f..345432d606 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -198,16 +198,16 @@ fn record_simulation_outcome( }, ) => tracing::warn!( ?order_uid, - signature = "pass", - simulation = "fail", + signature = signature.label(), + simulation = simulation.label(), ?reason, ?tenderly_url, "eip1271 simulation disagreement", ), (SignatureOutcome::Fail, SimulationOutcome::Pass) => tracing::warn!( ?order_uid, - signature = "fail", - simulation = "pass", + signature = signature.label(), + simulation = simulation.label(), "eip1271 simulation disagreement", ), (_, SimulationOutcome::Infra(err)) => { From 4c25c46e89f9f7c077ad4b8f9a447b6b4eabbddb Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 21 Apr 2026 21:55:56 +0100 Subject: [PATCH 030/154] Address Claude-review feedback on PR 4355 - 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. --- crates/configs/src/orderbook/mod.rs | 10 ++++++++++ crates/shared/src/order_validation.rs | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 82f47adc8d..a12951d592 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -489,6 +489,16 @@ eip1271-simulation-timeout = "5s" assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(5)); } + #[test] + fn parses_simulation_mode_shadow() { + let toml = r#" +gas-limit = "0x1000000" +eip1271-simulation-mode = "shadow" +"#; + let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); + assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Shadow); + } + #[test] fn parses_simulation_mode_disabled() { let toml = r#" diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 345432d606..f638898d93 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -76,14 +76,16 @@ pub trait Eip1271Simulating: Send + Sync { } /// Mode controlling whether the EIP-1271 order simulation can reject orders. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +/// The operational default lives in `configs::orderbook::Eip1271SimulationMode` +/// (currently `Disabled`); this enum only represents the on-path states +/// `OrderValidator` actually executes. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Eip1271SimulationMode { - /// Log disagreements, emit metrics. Never reject. **Default.** - #[default] + /// Log disagreements, emit metrics. Never reject. Shadow, - /// If the signature check passes but the order simulation fails, reject the - /// order with `ValidationError::SimulationFailed`. Infra errors still - /// never reject (fail-open). + /// If the signature check passes but the order simulation fails, reject + /// the order with `ValidationError::SimulationFailed`. Infra errors + /// still never reject (fail-open). Enforce, } @@ -244,7 +246,7 @@ async fn run_eip1271_simulation_only(config: &Eip1271Simulator, preview_order: & SimulationOutcome::Fail { reason, tenderly_url, - } => tracing::info!( + } => tracing::warn!( order_uid = %preview_order.metadata.uid, ?reason, ?tenderly_url, @@ -610,6 +612,9 @@ impl OrderValidator { let simulation_timeout = config.timeout; let order = preview_order.clone(); let simulation_fut = async move { + let _timer = Eip1271SimulationMetrics::get() + .simulation_time + .start_timer(); tokio::time::timeout(simulation_timeout, sim.simulate(&order)) .await .unwrap_or_else(|_| { @@ -619,12 +624,7 @@ impl OrderValidator { }) }; - let (signature_res, simulation_res) = { - let _timer = Eip1271SimulationMetrics::get() - .simulation_time - .start_timer(); - tokio::join!(signature_fut, simulation_fut) - }; + let (signature_res, simulation_res) = tokio::join!(signature_fut, simulation_fut); let signature_outcome = classify_signature(&signature_res); let simulation_outcome = classify_simulation(&simulation_res); From ab8357bd972a153f05b1f4aadf1c7b0d0d91e278 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 07:41:13 +0000 Subject: [PATCH 031/154] initial commit --- crates/simulator/src/lib.rs | 2 + crates/simulator/src/simulation_builder.rs | 261 ++++++++++++++++++ .../simulator/src/state_override_helpers.rs | 60 ++++ 3 files changed, 323 insertions(+) create mode 100644 crates/simulator/src/simulation_builder.rs create mode 100644 crates/simulator/src/state_override_helpers.rs diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index e3f98f0362..83f251e223 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -1,5 +1,7 @@ pub mod encoding; pub mod ethereum; +pub mod simulation_builder; +pub mod state_override_helpers; pub mod swap_simulator; pub mod tenderly; mod utils; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs new file mode 100644 index 0000000000..d7b5126774 --- /dev/null +++ b/crates/simulator/src/simulation_builder.rs @@ -0,0 +1,261 @@ +use { + crate::encoding::{ + EncodedSettlement, + Interaction, + Interactions, + WrapperCall, + encode_interactions, + encode_trade, + encode_wrapper_settlement, + }, + alloy_primitives::{Address, Bytes, U256}, + alloy_rpc_types::{ + TransactionRequest, + state::{AccountOverride, StateOverride}, + }, + model::{ + order::{OrderData, OrderKind}, + signature::{Signature, SigningScheme}, + }, +}; + +/// A simulator-specific order that bundles the data needed to encode a trade. +/// +/// Construct with [`Order::new`] and add optional fields via the builder +/// methods. Defaults to an EIP-1271 signature (pairs with [`FakeUser`] for +/// simulations that need to bypass signature verification). +pub struct Order { + data: OrderData, + owner: Address, + signature: Signature, + pre_interactions: Vec, + post_interactions: Vec, +} + +impl Order { + pub fn new(data: OrderData) -> Self { + Self { + data, + owner: Address::ZERO, + signature: Signature::default_with(SigningScheme::Eip1271), + pre_interactions: vec![], + post_interactions: vec![], + } + } + + pub fn with_signature(mut self, owner: Address, signature: Signature) -> Self { + self.owner = owner; + self.signature = signature; + self + } + + pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { + self.pre_interactions = interactions; + self + } + + pub fn with_post_interactions(mut self, interactions: Vec) -> Self { + self.post_interactions = interactions; + self + } +} + +/// Configuration for wrapping the settlement in a flashloan or custom wrapper +/// contract chain. +pub enum WrapperConfig { + Flashloan { router: Address, data: Bytes }, + Custom(Vec), +} + +/// How clearing prices are determined for the encoded settlement. +pub enum Prices { + /// Derive clearing prices directly from the order's limit price. + /// + /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = + /// sell_amount`, exactly satisfying the order's limit with no surplus. + Limit, + /// Explicit token list and matching clearing prices. + Explicit { + tokens: Vec
, + clearing_prices: Vec, + }, +} + +/// The output of [`SimulationBuilder::build`]: a transaction request and state +/// overrides ready to be passed to an alloy provider for simulation. +pub struct SettlementCall { + pub request: TransactionRequest, + pub state_overrides: StateOverride, +} + +/// Assembles a GPv2 settlement call for simulation purposes. +/// +/// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. +#[derive(Default)] +pub struct SimulationBuilder { + order: Option, + pre_interactions: Vec, + main_interactions: Vec, + post_interactions: Vec, + wrapper: Option, + prices: Option, + solver: Option
, + state_overrides: StateOverride, +} + +impl SimulationBuilder { + pub fn add_order(mut self, order: Order) -> Self { + self.order = Some(order); + self + } + + /// Mutate the pre-interactions list via a closure. The order's own + /// pre-hooks are prepended automatically in [`build`]; these interactions + /// follow them. + pub fn with_pre_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { + f(&mut self.pre_interactions); + self + } + + pub fn with_main_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { + f(&mut self.main_interactions); + self + } + + /// Mutate the post-interactions list via a closure. The order's own + /// post-hooks are appended automatically in [`build`]; these interactions + /// precede them. + pub fn with_post_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { + f(&mut self.post_interactions); + self + } + + pub fn with_wrapper(mut self, wrapper: WrapperConfig) -> Self { + self.wrapper = Some(wrapper); + self + } + + pub fn with_prices(mut self, prices: Prices) -> Self { + self.prices = Some(prices); + self + } + + pub fn from_solver(mut self, solver: Address) -> Self { + self.solver = Some(solver); + self + } + + pub fn state_override( + mut self, + address: Address, + account_override: impl Into, + ) -> Self { + self.state_overrides + .insert(address, account_override.into()); + self + } + + pub fn build(self, settlement_address: Address) -> Result { + self.build_with_modifications(settlement_address, |_| {}) + } + + pub fn build_with_modifications( + self, + settlement_address: Address, + customize: impl FnOnce(&mut EncodedSettlement), + ) -> Result { + let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; + + let (tokens, clearing_prices) = match &self.prices { + Some(Prices::Explicit { + tokens, + clearing_prices, + }) => (tokens.clone(), clearing_prices.clone()), + // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. + // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. + _ => ( + vec![order.data.sell_token, order.data.buy_token], + vec![order.data.buy_amount, order.data.sell_amount], + ), + }; + + let sell_token_index = tokens + .iter() + .position(|t| *t == order.data.sell_token) + .ok_or(BuildError::MissingSellToken)?; + let buy_token_index = tokens + .iter() + .position(|t| *t == order.data.buy_token) + .ok_or(BuildError::MissingBuyToken)?; + + let executed_amount = match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }; + + let trade = encode_trade( + &order.data, + &order.signature, + order.owner, + sell_token_index, + buy_token_index, + executed_amount, + ); + + let order_pre = &order.pre_interactions; + let order_post = &order.post_interactions; + + let mut settlement = EncodedSettlement { + tokens, + clearing_prices, + trades: vec![trade], + interactions: Interactions { + // order's pre-hooks run before any additional pre-interactions + pre: encode_interactions(order_pre.iter().chain(&self.pre_interactions)), + main: encode_interactions(&self.main_interactions), + // additional post-interactions run before the order's post-hooks + post: encode_interactions(self.post_interactions.iter().chain(order_post)), + }, + }; + + customize(&mut settlement); + + let settle_calldata = settlement.into_settle_call(); + + let (to, input) = match self.wrapper { + Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { + encode_wrapper_settlement(&wrappers, settle_calldata) + .expect("wrappers is non-empty") + } + Some(WrapperConfig::Flashloan { router, data }) => encode_wrapper_settlement( + &[WrapperCall { + address: router, + data, + }], + settle_calldata, + ) + .expect("wrappers is non-empty"), + _ => (settlement_address, settle_calldata), + }; + + Ok(SettlementCall { + request: TransactionRequest { + from: self.solver, + to: Some(to.into()), + input: input.into(), + ..Default::default() + }, + state_overrides: self.state_overrides, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum BuildError { + #[error("no order was added")] + NoOrder, + #[error("sell token not found in token list")] + MissingSellToken, + #[error("buy token not found in token list")] + MissingBuyToken, +} diff --git a/crates/simulator/src/state_override_helpers.rs b/crates/simulator/src/state_override_helpers.rs new file mode 100644 index 0000000000..ea835a36a2 --- /dev/null +++ b/crates/simulator/src/state_override_helpers.rs @@ -0,0 +1,60 @@ +use {alloy_primitives::Bytes, alloy_rpc_types::state::AccountOverride}; +pub use { + balance_overrides::{BalanceOverrideRequest, BalanceOverrides, BalanceOverriding}, + configs::balance_overrides::Strategy, +}; + +/// Deploys a fake ERC-1271 contract at a given address so that signature +/// verification succeeds unconditionally. Pass to +/// [`crate::simulation_builder::SimulationBuilder::state_override`] with the +/// order owner's address. +pub struct FakeUser { + pub code: Bytes, +} + +impl Default for FakeUser { + fn default() -> Self { + // Returns bytes4(0x1626ba7e) — the ERC-1271 magic value — for any call. + // Opcodes: PUSH4 1626ba7e | PUSH1 e0 | SHL | PUSH1 00 | MSTORE | PUSH1 20 | + // PUSH1 00 | RETURN + Self { + code: Bytes::from_static(&[ + 0x63, 0x16, 0x26, 0xba, 0x7e, // PUSH4 0x1626ba7e + 0x60, 0xe0, // PUSH1 224 + 0x1b, // SHL → 0x1626ba7e left-aligned in 32-byte word + 0x60, 0x00, // PUSH1 0x00 + 0x52, // MSTORE + 0x60, 0x20, // PUSH1 0x20 (return 32 bytes) + 0x60, 0x00, // PUSH1 0x00 (from offset 0) + 0xf3, // RETURN + ]), + } + } +} + +impl From for AccountOverride { + fn from(fake_user: FakeUser) -> Self { + Self { + code: Some(fake_user.code), + ..Default::default() + } + } +} + +/// Deploys the `AnyoneAuthenticator` contract at a given address, causing it +/// to approve any solver. Pass to +/// [`crate::simulation_builder::SimulationBuilder::state_override`] +/// with the authenticator contract's address. +pub struct AnyoneAuthenticator; + +impl From for AccountOverride { + fn from(_: AnyoneAuthenticator) -> Self { + Self { + code: Some( + contracts::support::AnyoneAuthenticator::AnyoneAuthenticator::DEPLOYED_BYTECODE + .clone(), + ), + ..Default::default() + } + } +} From 250f5daa77e94f6afdcb9ebcd6abb93cdceea1eb Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 22 Apr 2026 09:30:27 +0100 Subject: [PATCH 032/154] Convert order_simulator::Error via From impl for ? ergonomics --- crates/orderbook/src/eip1271_simulation.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index 9bd97ec918..2517fb6639 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -25,16 +25,8 @@ impl OrderSimulatorAdapter { #[async_trait] impl Eip1271Simulating for OrderSimulatorAdapter { async fn simulate(&self, order: &Order) -> Result<(), Eip1271SimulationError> { - let swap = self - .inner - .encode_order(order, Vec::new(), None) - .await - .map_err(map_simulator_err)?; - let result = self - .inner - .simulate_swap(swap, None) - .await - .map_err(map_simulator_err)?; + let swap = self.inner.encode_order(order, Vec::new(), None).await?; + let result = self.inner.simulate_swap(swap, None).await?; match result.error { None => Ok(()), Some(reason) => Err(Eip1271SimulationError::Reverted { @@ -45,10 +37,12 @@ impl Eip1271Simulating for OrderSimulatorAdapter { } } -fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimulationError { - match err { - order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => { - Eip1271SimulationError::Infra(e) +impl From for Eip1271SimulationError { + fn from(err: order_simulator::Error) -> Self { + match err { + order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => { + Self::Infra(e) + } } } } From c51a522231bc3361d79eb734c5839b38f1ea174a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 22 Apr 2026 09:49:02 +0100 Subject: [PATCH 033/154] Review comments --- crates/configs/src/orderbook/mod.rs | 24 +++++---- crates/orderbook/src/eip1271_simulation.rs | 2 +- crates/shared/src/order_validation.rs | 57 ++++++++++++++++------ 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index a12951d592..0f1139436f 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -469,9 +469,7 @@ mod tests { #[test] fn parses_simulation_mode_default() { - let toml = r#" -gas-limit = "0x1000000" -"#; + let toml = r#"gas-limit = "0x1000000""#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(2)); @@ -480,10 +478,10 @@ gas-limit = "0x1000000" #[test] fn parses_simulation_mode_enforce() { let toml = r#" -gas-limit = "0x1000000" -eip1271-simulation-mode = "enforce" -eip1271-simulation-timeout = "5s" -"#; + gas-limit = "0x1000000" + eip1271-simulation-mode = "enforce" + eip1271-simulation-timeout = "5s" + "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Enforce); assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(5)); @@ -492,9 +490,9 @@ eip1271-simulation-timeout = "5s" #[test] fn parses_simulation_mode_shadow() { let toml = r#" -gas-limit = "0x1000000" -eip1271-simulation-mode = "shadow" -"#; + gas-limit = "0x1000000" + eip1271-simulation-mode = "shadow" + "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Shadow); } @@ -502,9 +500,9 @@ eip1271-simulation-mode = "shadow" #[test] fn parses_simulation_mode_disabled() { let toml = r#" -gas-limit = "0x1000000" -eip1271-simulation-mode = "disabled" -"#; + gas-limit = "0x1000000" + eip1271-simulation-mode = "disabled" + "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); } diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index 2517fb6639..f3fbb1769d 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -28,11 +28,11 @@ impl Eip1271Simulating for OrderSimulatorAdapter { let swap = self.inner.encode_order(order, Vec::new(), None).await?; let result = self.inner.simulate_swap(swap, None).await?; match result.error { - None => Ok(()), Some(reason) => Err(Eip1271SimulationError::Reverted { reason, tenderly_url: result.tenderly_url, }), + None => Ok(()), } } } diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index f638898d93..1edd15a6df 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -227,15 +227,7 @@ async fn run_eip1271_simulation_only(config: &Eip1271Simulator, preview_order: & tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await }; let outcome = match res { - Ok(Ok(())) => SimulationOutcome::Pass, - Ok(Err(Eip1271SimulationError::Reverted { - reason, - tenderly_url, - })) => SimulationOutcome::Fail { - reason, - tenderly_url, - }, - Ok(Err(Eip1271SimulationError::Infra(err))) => SimulationOutcome::Infra(err), + Ok(inner) => classify_simulation(&inner), Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), }; Eip1271SimulationMetrics::get() @@ -584,6 +576,16 @@ impl OrderValidator { } } + /// Entry point for the EIP-1271 block of `validate_and_construct_order`. + /// + /// Two paths, depending on the `eip1271_skip_creation_validation` flag: + /// + /// - **Skipped**: the cheap `isValidSignature` check is bypassed by the + /// operator, and we return a `verification_gas_limit` of `0` (no gas was + /// spent on-chain verifying the signature). If the optional + /// `Eip1271Simulator` is configured, the full simulation still runs for + /// observability only and can never reject. + /// - **Not skipped**: delegates to `run_eip1271_with_signature_check`. async fn run_eip1271_checks( &self, check: SignatureCheck, @@ -596,7 +598,27 @@ impl OrderValidator { } return Ok(0u64); } + self.run_eip1271_with_signature_check(check, preview_order, hash) + .await + } + /// Runs the cheap `isValidSignature` check and, when a simulator is + /// configured, the full order simulation concurrently. Decides the + /// outcome: + /// + /// - signature `Invalid` → `InvalidEip1271Signature` (today's behaviour). + /// - signature `Other` → `ValidationError::Other` (infra error). + /// - signature `Ok(gas)` + simulation `Reverted` + **Enforce** mode → + /// `SimulationFailed(reason)` (the new rejection added by this PR). + /// - signature `Ok(gas)` in every other combination → `Ok(gas)`. + /// + /// Simulation infra errors (RPC / Tenderly / timeout) never reject. + async fn run_eip1271_with_signature_check( + &self, + check: SignatureCheck, + preview_order: &Order, + hash: B256, + ) -> Result { let signature_fut = self .signature_validator .validate_signature_and_get_additional_gas(check); @@ -1351,7 +1373,7 @@ mod tests { crate::order_quoting::{FindQuoteError, MockOrderQuoting}, account_balances::MockBalanceFetching, alloy::{ - primitives::{Address, U160, address, b256}, + primitives::{Address, U160, U256, address, b256}, providers::{Provider, ProviderBuilder, mock::Asserter}, signers::local::PrivateKeySigner, }, @@ -2915,9 +2937,9 @@ mod tests { valid_to: time::now_in_epoch_seconds() + 2, sell_token: Address::with_last_byte(1), buy_token: Address::with_last_byte(2), - buy_amount: alloy::primitives::U256::from(1), - sell_amount: alloy::primitives::U256::from(1), - fee_amount: alloy::primitives::U256::ZERO, + buy_amount: U256::ONE, + sell_amount: U256::ONE, + fee_amount: U256::ZERO, from: Some(Address::repeat_byte(1)), signature: Signature::Eip1271(vec![1, 2, 3]), app_data: OrderCreationAppData::Full { @@ -2943,6 +2965,9 @@ mod tests { eip1271_simulator: Option, eip1271_skip_creation_validation: bool, ) -> OrderValidator { + // The quote lookup, balance fetch, and limit-order count are off the + // path under test here — stub them to always succeed so every test + // reaches the EIP-1271 block without tripping earlier validation. let mut order_quoter = MockOrderQuoting::new(); order_quoter .expect_find_quote() @@ -2953,7 +2978,8 @@ mod tests { .returning(|_, _| Ok(())); let mut limit_order_counter = MockLimitOrderCounting::new(); limit_order_counter.expect_count().returning(|_| Ok(0u64)); - let native_token = WETH9::Instance::new([0xef; 20].into(), ethrpc::mock::web3().provider); + let native_token = + WETH9::Instance::new(Address::repeat_byte(0xef), ethrpc::mock::web3().provider); OrderValidator::new( native_token, Arc::new(order_validation::banned::Users::none()), @@ -3115,6 +3141,9 @@ mod tests { #[tokio::test] async fn skip_flag_runs_simulation_only_and_never_rejects() { let mut signature_validator = MockSignatureValidating::new(); + // With `eip1271_skip_creation_validation = true`, the signature + // validator must not be called. `.times(0)` makes mockall panic the + // test if it is. signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); From 7bd25923a8342aceaff44f836b1bc66d8f399e20 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 22 Apr 2026 10:06:54 +0100 Subject: [PATCH 034/154] Nit --- crates/shared/src/order_validation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 1edd15a6df..c8fa98dfec 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -3142,8 +3142,7 @@ mod tests { async fn skip_flag_runs_simulation_only_and_never_rejects() { let mut signature_validator = MockSignatureValidating::new(); // With `eip1271_skip_creation_validation = true`, the signature - // validator must not be called. `.times(0)` makes mockall panic the - // test if it is. + // validator must not be called. signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); From ea11e37452aea3d16e750ffb644ae70523fd9836 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 22 Apr 2026 10:26:26 +0100 Subject: [PATCH 035/154] Add SignatureCheck::new constructor --- crates/cow-amm/src/amm.rs | 14 ++--- crates/shared/src/order_validation.rs | 82 ++++++++++++++------------- crates/signature-validator/src/lib.rs | 16 ++++++ 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/crates/cow-amm/src/amm.rs b/crates/cow-amm/src/amm.rs index 738ada502c..4e39f36867 100644 --- a/crates/cow-amm/src/amm.rs +++ b/crates/cow-amm/src/amm.rs @@ -67,13 +67,13 @@ impl Amm { // To avoid issues caused by that we check the validity of the signature. let hash = hashed_eip712_message(domain_separator, &template.order.hash_struct()); validator - .validate_signature_and_get_additional_gas(SignatureCheck { - signer: self.address, - hash: hash.0, - signature: template.signature.to_bytes(), - interactions: template.pre_interactions.clone(), - balance_override: None, - }) + .validate_signature_and_get_additional_gas(SignatureCheck::new( + self.address, + hash.0, + template.signature.to_bytes(), + template.pre_interactions.clone(), + None, + )) .await .context("invalid signature")?; diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index c8fa98dfec..6af85986a8 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -963,33 +963,35 @@ impl OrderValidating for OrderValidator { }; let uid = data.uid(domain_separator, owner); - let verification_gas_limit = if let Signature::Eip1271(signature) = &order.signature { - let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); - let check = SignatureCheck { - signer: owner, - hash: hash.0, - signature: signature.to_owned(), - interactions: app_data.interactions.pre.clone(), - balance_override: app_data.inner.protocol.flashloan.as_ref().map(|loan| { - BalanceOverrideRequest { - token: loan.token, - holder: loan.receiver, - amount: loan.amount, - } - }), + let verification_gas_limit = + if let Signature::Eip1271(signature) = &order.signature { + let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); + let check = + SignatureCheck::new( + owner, + hash.0, + signature.to_owned(), + app_data.interactions.pre.clone(), + app_data.inner.protocol.flashloan.as_ref().map(|loan| { + BalanceOverrideRequest { + token: loan.token, + holder: loan.receiver, + amount: loan.amount, + } + }), + ); + let preview_order = build_preview_order_for_sim( + &data, + &app_data.interactions, + owner, + uid, + order.signature.clone(), + ); + self.run_eip1271_checks(check, &preview_order, hash).await? + } else { + // in any other case, just apply 0 + 0u64 }; - let preview_order = build_preview_order_for_sim( - &data, - &app_data.interactions, - owner, - uid, - order.signature.clone(), - ); - self.run_eip1271_checks(check, &preview_order, hash).await? - } else { - // in any other case, just apply 0 - 0u64 - }; if data.buy_amount.is_zero() || data.sell_amount.is_zero() { return Err(ValidationError::ZeroAmount); @@ -1815,13 +1817,13 @@ mod tests { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() - .with(eq(SignatureCheck { - signer: creation.from.unwrap(), - hash: order_hash.0, - signature: vec![1, 2, 3], - interactions: pre_interactions.clone(), - balance_override: None, - })) + .with(eq(SignatureCheck::new( + creation.from.unwrap(), + order_hash.0, + vec![1, 2, 3], + pre_interactions.clone(), + None, + ))) .returning(|_| Ok(0u64)); let validator = OrderValidator { @@ -1844,13 +1846,13 @@ mod tests { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() - .with(eq(SignatureCheck { - signer: creation.from.unwrap(), - hash: order_hash.0, - signature: vec![1, 2, 3], - interactions: pre_interactions.clone(), - balance_override: None, - })) + .with(eq(SignatureCheck::new( + creation.from.unwrap(), + order_hash.0, + vec![1, 2, 3], + pre_interactions.clone(), + None, + ))) .returning(|_| Err(SignatureValidationError::Invalid)); let validator = OrderValidator { diff --git a/crates/signature-validator/src/lib.rs b/crates/signature-validator/src/lib.rs index 8f1a20cfb6..819adbf7cc 100644 --- a/crates/signature-validator/src/lib.rs +++ b/crates/signature-validator/src/lib.rs @@ -22,6 +22,22 @@ pub struct SignatureCheck { } impl SignatureCheck { + pub fn new( + signer: Address, + hash: [u8; 32], + signature: Vec, + interactions: Vec, + balance_override: Option, + ) -> Self { + Self { + signer, + hash, + signature, + interactions, + balance_override, + } + } + /// A signature check requires setup when there are interactions to be taken /// into account or when the balance override is set. /// From 76b12c6ef26153a6681a300b5e45fee332d60eee Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 09:34:37 +0000 Subject: [PATCH 036/154] fixup --- .../simulator/src/state_override_helpers.rs | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/crates/simulator/src/state_override_helpers.rs b/crates/simulator/src/state_override_helpers.rs index ea835a36a2..d30ec72607 100644 --- a/crates/simulator/src/state_override_helpers.rs +++ b/crates/simulator/src/state_override_helpers.rs @@ -8,34 +8,22 @@ pub use { /// verification succeeds unconditionally. Pass to /// [`crate::simulation_builder::SimulationBuilder::state_override`] with the /// order owner's address. -pub struct FakeUser { - pub code: Bytes, -} - -impl Default for FakeUser { - fn default() -> Self { - // Returns bytes4(0x1626ba7e) — the ERC-1271 magic value — for any call. - // Opcodes: PUSH4 1626ba7e | PUSH1 e0 | SHL | PUSH1 00 | MSTORE | PUSH1 20 | - // PUSH1 00 | RETURN - Self { - code: Bytes::from_static(&[ - 0x63, 0x16, 0x26, 0xba, 0x7e, // PUSH4 0x1626ba7e - 0x60, 0xe0, // PUSH1 224 - 0x1b, // SHL → 0x1626ba7e left-aligned in 32-byte word - 0x60, 0x00, // PUSH1 0x00 - 0x52, // MSTORE - 0x60, 0x20, // PUSH1 0x20 (return 32 bytes) - 0x60, 0x00, // PUSH1 0x00 (from offset 0) - 0xf3, // RETURN - ]), - } - } -} +pub struct FakeUser; impl From for AccountOverride { - fn from(fake_user: FakeUser) -> Self { + fn from(_fake_user: FakeUser) -> Self { + let code = Bytes::from_static(&[ + 0x63, 0x16, 0x26, 0xba, 0x7e, // PUSH4 0x1626ba7e + 0x60, 0xe0, // PUSH1 224 + 0x1b, // SHL → 0x1626ba7e left-aligned in 32-byte word + 0x60, 0x00, // PUSH1 0x00 + 0x52, // MSTORE + 0x60, 0x20, // PUSH1 0x20 (return 32 bytes) + 0x60, 0x00, // PUSH1 0x00 (from offset 0) + 0xf3, // RETURN + ]); Self { - code: Some(fake_user.code), + code: Some(code), ..Default::default() } } From ebfd4a93ea539908f560af20987f0d6239790eb7 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 22 Apr 2026 10:54:38 +0100 Subject: [PATCH 037/154] Pull timeout handling out of simulation_fut in run_eip1271_with_signature_check Same separation now used in run_eip1271_simulation_only. The simulation future returns the raw timeout Result; the outer match splits timeout (Infra) from the inner classification. --- crates/shared/src/order_validation.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 6af85986a8..109b638797 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -637,19 +637,16 @@ impl OrderValidator { let _timer = Eip1271SimulationMetrics::get() .simulation_time .start_timer(); - tokio::time::timeout(simulation_timeout, sim.simulate(&order)) - .await - .unwrap_or_else(|_| { - Err(Eip1271SimulationError::Infra(anyhow!( - "eip1271 simulation timeout" - ))) - }) + tokio::time::timeout(simulation_timeout, sim.simulate(&order)).await }; let (signature_res, simulation_res) = tokio::join!(signature_fut, simulation_fut); let signature_outcome = classify_signature(&signature_res); - let simulation_outcome = classify_simulation(&simulation_res); + let simulation_outcome = match simulation_res { + Ok(inner) => classify_simulation(&inner), + Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), + }; record_simulation_outcome( signature_outcome, &simulation_outcome, From d410031f5c262879cdb84cd1e8407388188a07a8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 09:57:47 +0000 Subject: [PATCH 038/154] fixup --- crates/simulator/src/encoding.rs | 2 +- crates/simulator/src/simulation_builder.rs | 73 +++++++++++++++------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index 8c6d188c24..d318450341 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, B256, Bytes, U256}, alloy_sol_types::SolCall, app_data::AppDataHash, - contracts::GPv2Settlement, + contracts::{FlashLoanRouter::LoanRequest, GPv2Settlement}, derive_more::Debug, model::{ interaction::InteractionData, diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index d7b5126774..f79f0622ed 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -8,11 +8,12 @@ use { encode_trade, encode_wrapper_settlement, }, - alloy_primitives::{Address, Bytes, U256}, + alloy_primitives::{Address, U256}, alloy_rpc_types::{ TransactionRequest, state::{AccountOverride, StateOverride}, }, + alloy_sol_types::SolCall, model::{ order::{OrderData, OrderKind}, signature::{Signature, SigningScheme}, @@ -60,10 +61,20 @@ impl Order { } } +pub struct FlashloanRequest { + pub amount: U256, + pub borrower: Address, + pub lender: Address, + pub token: Address, +} + /// Configuration for wrapping the settlement in a flashloan or custom wrapper /// contract chain. pub enum WrapperConfig { - Flashloan { router: Address, data: Bytes }, + Flashloan { + router: Address, + loans: Vec, + }, Custom(Vec), } @@ -100,6 +111,7 @@ pub struct SimulationBuilder { wrapper: Option, prices: Option, solver: Option
, + auction_id: Option, state_overrides: StateOverride, } @@ -109,24 +121,18 @@ impl SimulationBuilder { self } - /// Mutate the pre-interactions list via a closure. The order's own - /// pre-hooks are prepended automatically in [`build`]; these interactions - /// follow them. - pub fn with_pre_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { - f(&mut self.pre_interactions); + pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { + self.pre_interactions = interactions; self } - pub fn with_main_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { - f(&mut self.main_interactions); + pub fn with_main_interactions(mut self, interactions: Vec) -> Self { + self.main_interactions = interactions; self } - /// Mutate the post-interactions list via a closure. The order's own - /// post-hooks are appended automatically in [`build`]; these interactions - /// precede them. - pub fn with_post_interactions_mut(mut self, f: impl FnOnce(&mut Vec)) -> Self { - f(&mut self.post_interactions); + pub fn with_post_interactions(mut self, interactions: Vec) -> Self { + self.post_interactions = interactions; self } @@ -145,6 +151,11 @@ impl SimulationBuilder { self } + pub fn with_auction_id(mut self, id: i64) -> Self { + self.auction_id = Some(id); + self + } + pub fn state_override( mut self, address: Address, @@ -220,21 +231,37 @@ impl SimulationBuilder { customize(&mut settlement); - let settle_calldata = settlement.into_settle_call(); + let settle_calldata = { + let mut bytes = settlement.into_settle_call().to_vec(); + if let Some(id) = self.auction_id { + bytes.extend_from_slice(&id.to_be_bytes()); + } + bytes.into() + }; let (to, input) = match self.wrapper { Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata) .expect("wrappers is non-empty") } - Some(WrapperConfig::Flashloan { router, data }) => encode_wrapper_settlement( - &[WrapperCall { - address: router, - data, - }], - settle_calldata, - ) - .expect("wrappers is non-empty"), + Some(WrapperConfig::Flashloan { router, loans }) => { + let calldata = + contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { + loans: loans + .into_iter() + .map(|l| contracts::FlashLoanRouter::LoanRequest::Data { + amount: l.amount, + borrower: l.borrower, + lender: l.lender, + token: l.token, + }) + .collect(), + settlement: settle_calldata, + } + .abi_encode() + .into(); + (router, calldata) + } _ => (settlement_address, settle_calldata), }; From 417c9a0d8e8e2954bd269ffd98cec0c6001eddaa Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 10:22:12 +0000 Subject: [PATCH 039/154] introduce Solver enum --- crates/simulator/src/simulation_builder.rs | 38 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f79f0622ed..df4f353cfe 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -68,6 +68,14 @@ pub struct FlashloanRequest { pub token: Address, } +pub enum Solver { + /// A real allow-listed solver address. Used as-is with no state overrides. + Real(Address), + /// A fake solver for simulation. Uses the provided address or generates a + /// random one, then sets its ETH balance to `U256::MAX / 2`. + Fake(Option
), +} + /// Configuration for wrapping the settlement in a flashloan or custom wrapper /// contract chain. pub enum WrapperConfig { @@ -110,7 +118,7 @@ pub struct SimulationBuilder { post_interactions: Vec, wrapper: Option, prices: Option, - solver: Option
, + solver: Option, auction_id: Option, state_overrides: StateOverride, } @@ -146,7 +154,7 @@ impl SimulationBuilder { self } - pub fn from_solver(mut self, solver: Address) -> Self { + pub fn from_solver(mut self, solver: Solver) -> Self { self.solver = Some(solver); self } @@ -265,14 +273,34 @@ impl SimulationBuilder { _ => (settlement_address, settle_calldata), }; + let mut state_overrides = self.state_overrides; + let from = match self.solver { + Some(Solver::Real(addr)) => addr, + Some(Solver::Fake(opt)) => { + let addr = opt.unwrap_or_else(Address::random); + state_overrides.insert( + addr, + AccountOverride { + balance: Some(U256::MAX / U256::from(2)), + ..Default::default() + }, + ); + // TODO: override the settlement's authenticator with AnyoneAuthenticator + // so this address is accepted as a solver. Requires knowing the + // authenticator contract address, which isn't available here yet. + addr + } + None => return Err(BuildError::NoSolver), + }; + Ok(SettlementCall { request: TransactionRequest { - from: self.solver, + from: Some(from), to: Some(to.into()), input: input.into(), ..Default::default() }, - state_overrides: self.state_overrides, + state_overrides, }) } } @@ -281,6 +309,8 @@ impl SimulationBuilder { pub enum BuildError { #[error("no order was added")] NoOrder, + #[error("no solver was set")] + NoSolver, #[error("sell token not found in token list")] MissingSellToken, #[error("buy token not found in token list")] From 19926d90ac55233b4ba65bb4d485e42d5c957470 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 10:46:10 +0000 Subject: [PATCH 040/154] factory --- crates/simulator/src/simulation_builder.rs | 67 +++++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index df4f353cfe..cbee7c3dc1 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,12 +1,15 @@ use { - crate::encoding::{ - EncodedSettlement, - Interaction, - Interactions, - WrapperCall, - encode_interactions, - encode_trade, - encode_wrapper_settlement, + crate::{ + encoding::{ + EncodedSettlement, + Interaction, + Interactions, + WrapperCall, + encode_interactions, + encode_trade, + encode_wrapper_settlement, + }, + state_override_helpers::AnyoneAuthenticator, }, alloy_primitives::{Address, U256}, alloy_rpc_types::{ @@ -14,10 +17,12 @@ use { state::{AccountOverride, StateOverride}, }, alloy_sol_types::SolCall, + anyhow::Result, model::{ order::{OrderData, OrderKind}, signature::{Signature, SigningScheme}, }, + std::sync::Arc, }; /// A simulator-specific order that bundles the data needed to encode a trade. @@ -121,6 +126,7 @@ pub struct SimulationBuilder { solver: Option, auction_id: Option, state_overrides: StateOverride, + simulator: Option, } impl SimulationBuilder { @@ -174,15 +180,18 @@ impl SimulationBuilder { self } - pub fn build(self, settlement_address: Address) -> Result { - self.build_with_modifications(settlement_address, |_| {}) + pub fn build(self) -> Result { + self.build_with_modifications(|_| {}) } pub fn build_with_modifications( self, - settlement_address: Address, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { + let simulator = self + .simulator + .as_ref() + .ok_or(BuildError::NoSettlementAddress)?; let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; let (tokens, clearing_prices) = match &self.prices { @@ -270,7 +279,7 @@ impl SimulationBuilder { .into(); (router, calldata) } - _ => (settlement_address, settle_calldata), + _ => (*simulator.0.settlement.address(), settle_calldata), }; let mut state_overrides = self.state_overrides; @@ -285,9 +294,7 @@ impl SimulationBuilder { ..Default::default() }, ); - // TODO: override the settlement's authenticator with AnyoneAuthenticator - // so this address is accepted as a solver. Requires knowing the - // authenticator contract address, which isn't available here yet. + state_overrides.insert(simulator.0.authenticator, AnyoneAuthenticator.into()); addr } None => return Err(BuildError::NoSolver), @@ -307,6 +314,8 @@ impl SimulationBuilder { #[derive(Debug, thiserror::Error)] pub enum BuildError { + #[error("no settlement address was set")] + NoSettlementAddress, #[error("no order was added")] NoOrder, #[error("no solver was set")] @@ -316,3 +325,31 @@ pub enum BuildError { #[error("buy token not found in token list")] MissingBuyToken, } + +struct Inner { + settlement: contracts::GPv2Settlement::Instance, + authenticator: Address, +} + +/// Holds the settlement contract and its authenticator address, and acts as a +/// factory for [`SimulationBuilder`] instances that are pre-configured with +/// these values. +#[derive(Clone)] +pub struct SettlementSimulator(Arc); + +impl SettlementSimulator { + pub async fn new(settlement: contracts::GPv2Settlement::Instance) -> Result { + let authenticator = Address(settlement.authenticator().call().await?.0); + Ok(Self(Arc::new(Inner { + settlement, + authenticator, + }))) + } + + pub fn new_simulation_builder(&self) -> SimulationBuilder { + SimulationBuilder { + simulator: Some(self.clone()), + ..Default::default() + } + } +} From 9731a30628fc0987421451a53ff9540feb4571fe Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 11:01:24 +0000 Subject: [PATCH 041/154] Only allow list specific solver address --- crates/simulator/src/simulation_builder.rs | 67 ++++++++++++++-------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index cbee7c3dc1..4d06b7ef09 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,17 +1,14 @@ use { - crate::{ - encoding::{ - EncodedSettlement, - Interaction, - Interactions, - WrapperCall, - encode_interactions, - encode_trade, - encode_wrapper_settlement, - }, - state_override_helpers::AnyoneAuthenticator, + crate::encoding::{ + EncodedSettlement, + Interaction, + Interactions, + WrapperCall, + encode_interactions, + encode_trade, + encode_wrapper_settlement, }, - alloy_primitives::{Address, U256}, + alloy_primitives::{Address, B256, U256, keccak256}, alloy_rpc_types::{ TransactionRequest, state::{AccountOverride, StateOverride}, @@ -115,7 +112,6 @@ pub struct SettlementCall { /// Assembles a GPv2 settlement call for simulation purposes. /// /// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. -#[derive(Default)] pub struct SimulationBuilder { order: Option, pre_interactions: Vec, @@ -126,7 +122,7 @@ pub struct SimulationBuilder { solver: Option, auction_id: Option, state_overrides: StateOverride, - simulator: Option, + simulator: SettlementSimulator, } impl SimulationBuilder { @@ -188,10 +184,6 @@ impl SimulationBuilder { self, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { - let simulator = self - .simulator - .as_ref() - .ok_or(BuildError::NoSettlementAddress)?; let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; let (tokens, clearing_prices) = match &self.prices { @@ -279,7 +271,7 @@ impl SimulationBuilder { .into(); (router, calldata) } - _ => (*simulator.0.settlement.address(), settle_calldata), + _ => (*self.simulator.0.settlement.address(), settle_calldata), }; let mut state_overrides = self.state_overrides; @@ -287,6 +279,7 @@ impl SimulationBuilder { Some(Solver::Real(addr)) => addr, Some(Solver::Fake(opt)) => { let addr = opt.unwrap_or_else(Address::random); + // give solver address enough ETH state_overrides.insert( addr, AccountOverride { @@ -294,7 +287,27 @@ impl SimulationBuilder { ..Default::default() }, ); - state_overrides.insert(simulator.0.authenticator, AnyoneAuthenticator.into()); + + // add address to solver allow-list + let target_slot = { + // authenticator stores a `mapping(address=>bool)` in storage + // slot 1 so we can compute precisely which storage slot we + // have to override + let mut buf = [0; 64]; + buf[12..32].copy_from_slice(addr.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + keccak256(buf) + }; + state_overrides.insert( + self.simulator.0.authenticator, + AccountOverride { + state_diff: Some( + // true is encoded as value with the last bit being 1 + std::iter::once((target_slot, B256::with_last_byte(1))).collect(), + ), + ..Default::default() + }, + ); addr } None => return Err(BuildError::NoSolver), @@ -314,8 +327,6 @@ impl SimulationBuilder { #[derive(Debug, thiserror::Error)] pub enum BuildError { - #[error("no settlement address was set")] - NoSettlementAddress, #[error("no order was added")] NoOrder, #[error("no solver was set")] @@ -348,8 +359,16 @@ impl SettlementSimulator { pub fn new_simulation_builder(&self) -> SimulationBuilder { SimulationBuilder { - simulator: Some(self.clone()), - ..Default::default() + simulator: self.clone(), + order: None, + pre_interactions: vec![], + main_interactions: vec![], + post_interactions: vec![], + wrapper: None, + prices: None, + solver: None, + auction_id: None, + state_overrides: StateOverride::default(), } } } From 0f03c74887bca942b08eb9e424b87d05b2232343 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 13:34:16 +0000 Subject: [PATCH 042/154] add function for funding settlement contract --- crates/simulator/src/simulation_builder.rs | 62 +++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 4d06b7ef09..2abe5c35c3 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -15,6 +15,7 @@ use { }, alloy_sol_types::SolCall, anyhow::Result, + balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, model::{ order::{OrderData, OrderKind}, signature::{Signature, SigningScheme}, @@ -71,10 +72,15 @@ pub struct FlashloanRequest { } pub enum Solver { - /// A real allow-listed solver address. Used as-is with no state overrides. + /// Simulation assumes this is an actual solver so no state overrides will + /// be applied to allow list it explicitly. + /// If you need a very specific solver setup for your simulation consider + /// using this and explicitly add the necessary state overrides yourself + /// with `Simulation::build_with_modifications()`. Real(Address), /// A fake solver for simulation. Uses the provided address or generates a - /// random one, then sets its ETH balance to `U256::MAX / 2`. + /// random one. The simulation builder will automatically set the required + /// state overrides to give it enough ETH and allow list it as a solver. Fake(Option
), } @@ -123,6 +129,7 @@ pub struct SimulationBuilder { auction_id: Option, state_overrides: StateOverride, simulator: SettlementSimulator, + fund_settlement_contract: bool, } impl SimulationBuilder { @@ -176,11 +183,19 @@ impl SimulationBuilder { self } - pub fn build(self) -> Result { - self.build_with_modifications(|_| {}) + /// Override the settlement contract's buy token balance so it can pay out + /// the order without any external liquidity. The required amount is derived + /// from the order's executed amount and clearing prices at `build()` time. + pub fn fund_settlement_contract(mut self) -> Self { + self.fund_settlement_contract = true; + self + } + + pub async fn build(self) -> Result { + self.build_with_modifications(|_| {}).await } - pub fn build_with_modifications( + pub async fn build_with_modifications( self, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { @@ -213,6 +228,17 @@ impl SimulationBuilder { OrderKind::Buy => order.data.buy_amount, }; + // Compute before clearing_prices is moved into EncodedSettlement below. + let fund_amount = self + .fund_settlement_contract + .then(|| match order.data.kind { + OrderKind::Sell => clearing_prices[sell_token_index] + .saturating_mul(executed_amount) + .checked_div(clearing_prices[buy_token_index]) + .unwrap_or(U256::MAX), + OrderKind::Buy => executed_amount, + }); + let trade = encode_trade( &order.data, &order.signature, @@ -248,6 +274,20 @@ impl SimulationBuilder { bytes.into() }; + let fund_override = if let Some(amount) = fund_amount { + self.simulator + .0 + .balance_overrides + .state_override(BalanceOverrideRequest { + token: order.data.buy_token, + holder: *self.simulator.0.settlement.address(), + amount, + }) + .await + } else { + None + }; + let (to, input) = match self.wrapper { Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata) @@ -313,6 +353,10 @@ impl SimulationBuilder { None => return Err(BuildError::NoSolver), }; + if let Some((addr, account_override)) = fund_override { + state_overrides.insert(addr, account_override); + } + Ok(SettlementCall { request: TransactionRequest { from: Some(from), @@ -340,6 +384,7 @@ pub enum BuildError { struct Inner { settlement: contracts::GPv2Settlement::Instance, authenticator: Address, + balance_overrides: Arc, } /// Holds the settlement contract and its authenticator address, and acts as a @@ -349,11 +394,15 @@ struct Inner { pub struct SettlementSimulator(Arc); impl SettlementSimulator { - pub async fn new(settlement: contracts::GPv2Settlement::Instance) -> Result { + pub async fn new( + settlement: contracts::GPv2Settlement::Instance, + balance_overrides: Arc, + ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); Ok(Self(Arc::new(Inner { settlement, authenticator, + balance_overrides, }))) } @@ -369,6 +418,7 @@ impl SettlementSimulator { solver: None, auction_id: None, state_overrides: StateOverride::default(), + fund_settlement_contract: false, } } } From afedc26ba8aa6a58274c3fa2382146d3761ac6df Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 14:21:00 +0000 Subject: [PATCH 043/154] add executed amounts --- crates/simulator/src/simulation_builder.rs | 62 +++++++++++++--------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 2abe5c35c3..504fad8174 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -34,6 +34,7 @@ pub struct Order { signature: Signature, pre_interactions: Vec, post_interactions: Vec, + executed_amount: Option, } impl Order { @@ -44,6 +45,7 @@ impl Order { signature: Signature::default_with(SigningScheme::Eip1271), pre_interactions: vec![], post_interactions: vec![], + executed_amount: None, } } @@ -62,6 +64,11 @@ impl Order { self.post_interactions = interactions; self } + + pub fn with_executed_amount(mut self, amount: U256) -> Self { + self.executed_amount = Some(amount); + self + } } pub struct FlashloanRequest { @@ -100,7 +107,10 @@ pub enum Prices { /// /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = /// sell_amount`, exactly satisfying the order's limit with no surplus. + /// This should NOT be used when encoding solutions you actually want + /// to submit. Limit, + // TODO: check how this can be made nicer. /// Explicit token list and matching clearing prices. Explicit { tokens: Vec
, @@ -191,21 +201,25 @@ impl SimulationBuilder { self } + /// Finishes the simulation struct based on the configuration thus far. pub async fn build(self) -> Result { self.build_with_modifications(|_| {}).await } + /// Same as `build()` but allows the caller to alter the simulation + /// before it gets finalized. This should only be used for very speicific + /// setups. pub async fn build_with_modifications( self, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; - let (tokens, clearing_prices) = match &self.prices { + let (tokens, clearing_prices) = match self.prices { Some(Prices::Explicit { tokens, clearing_prices, - }) => (tokens.clone(), clearing_prices.clone()), + }) => (tokens, clearing_prices), // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. _ => ( @@ -223,10 +237,12 @@ impl SimulationBuilder { .position(|t| *t == order.data.buy_token) .ok_or(BuildError::MissingBuyToken)?; - let executed_amount = match order.data.kind { - OrderKind::Sell => order.data.sell_amount, - OrderKind::Buy => order.data.buy_amount, - }; + let executed_amount = order + .executed_amount + .unwrap_or_else(|| match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }); // Compute before clearing_prices is moved into EncodedSettlement below. let fund_amount = self @@ -274,20 +290,6 @@ impl SimulationBuilder { bytes.into() }; - let fund_override = if let Some(amount) = fund_amount { - self.simulator - .0 - .balance_overrides - .state_override(BalanceOverrideRequest { - token: order.data.buy_token, - holder: *self.simulator.0.settlement.address(), - amount, - }) - .await - } else { - None - }; - let (to, input) = match self.wrapper { Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata) @@ -352,10 +354,20 @@ impl SimulationBuilder { } None => return Err(BuildError::NoSolver), }; - - if let Some((addr, account_override)) = fund_override { - state_overrides.insert(addr, account_override); - } + if let Some(amount) = fund_amount { + let (address, state_override) = self + .simulator + .0 + .balance_overrides + .state_override(BalanceOverrideRequest { + token: order.data.buy_token, + holder: *self.simulator.0.settlement.address(), + amount, + }) + .await + .ok_or(BuildError::FailedToOverrideBalances)?; + state_overrides.insert(address, state_override); + }; Ok(SettlementCall { request: TransactionRequest { @@ -379,6 +391,8 @@ pub enum BuildError { MissingSellToken, #[error("buy token not found in token list")] MissingBuyToken, + #[error("could not override token balances to fund settlement contract")] + FailedToOverrideBalances, } struct Inner { From db932ac8426f4d43a90a4ece056e08c8c72feee7 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 15:49:57 +0000 Subject: [PATCH 044/154] Move flashloan router address into simulator thingy --- crates/simulator/src/simulation_builder.rs | 60 +++++++------------ .../simulator/src/state_override_helpers.rs | 40 +++++++++---- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 504fad8174..b228fcf98a 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,14 +1,17 @@ use { - crate::encoding::{ - EncodedSettlement, - Interaction, - Interactions, - WrapperCall, - encode_interactions, - encode_trade, - encode_wrapper_settlement, + crate::{ + encoding::{ + EncodedSettlement, + Interaction, + Interactions, + WrapperCall, + encode_interactions, + encode_trade, + encode_wrapper_settlement, + }, + state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, }, - alloy_primitives::{Address, B256, U256, keccak256}, + alloy_primitives::{Address, U256}, alloy_rpc_types::{ TransactionRequest, state::{AccountOverride, StateOverride}, @@ -94,10 +97,7 @@ pub enum Solver { /// Configuration for wrapping the settlement in a flashloan or custom wrapper /// contract chain. pub enum WrapperConfig { - Flashloan { - router: Address, - loans: Vec, - }, + Flashloan { loans: Vec }, Custom(Vec), } @@ -295,7 +295,7 @@ impl SimulationBuilder { encode_wrapper_settlement(&wrappers, settle_calldata) .expect("wrappers is non-empty") } - Some(WrapperConfig::Flashloan { router, loans }) => { + Some(WrapperConfig::Flashloan { loans }) => { let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { loans: loans @@ -311,7 +311,7 @@ impl SimulationBuilder { } .abi_encode() .into(); - (router, calldata) + (self.simulator.0.flash_loan_router, calldata) } _ => (*self.simulator.0.settlement.address(), settle_calldata), }; @@ -321,34 +321,11 @@ impl SimulationBuilder { Some(Solver::Real(addr)) => addr, Some(Solver::Fake(opt)) => { let addr = opt.unwrap_or_else(Address::random); - // give solver address enough ETH - state_overrides.insert( - addr, - AccountOverride { - balance: Some(U256::MAX / U256::from(2)), - ..Default::default() - }, - ); + state_overrides.insert(addr, EthBalanceOverride(U256::MAX / U256::from(2)).into()); - // add address to solver allow-list - let target_slot = { - // authenticator stores a `mapping(address=>bool)` in storage - // slot 1 so we can compute precisely which storage slot we - // have to override - let mut buf = [0; 64]; - buf[12..32].copy_from_slice(addr.as_slice()); - buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); - keccak256(buf) - }; state_overrides.insert( self.simulator.0.authenticator, - AccountOverride { - state_diff: Some( - // true is encoded as value with the last bit being 1 - std::iter::once((target_slot, B256::with_last_byte(1))).collect(), - ), - ..Default::default() - }, + SolverAllowlisting(addr).into(), ); addr } @@ -398,6 +375,7 @@ pub enum BuildError { struct Inner { settlement: contracts::GPv2Settlement::Instance, authenticator: Address, + flash_loan_router: Address, balance_overrides: Arc, } @@ -410,12 +388,14 @@ pub struct SettlementSimulator(Arc); impl SettlementSimulator { pub async fn new( settlement: contracts::GPv2Settlement::Instance, + flash_loan_router: Address, balance_overrides: Arc, ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); Ok(Self(Arc::new(Inner { settlement, authenticator, + flash_loan_router, balance_overrides, }))) } diff --git a/crates/simulator/src/state_override_helpers.rs b/crates/simulator/src/state_override_helpers.rs index d30ec72607..b3aec19451 100644 --- a/crates/simulator/src/state_override_helpers.rs +++ b/crates/simulator/src/state_override_helpers.rs @@ -1,4 +1,8 @@ -use {alloy_primitives::Bytes, alloy_rpc_types::state::AccountOverride}; +use { + alloy_primitives::{Address, B256, Bytes, U256, keccak256}, + alloy_rpc_types::state::AccountOverride, + std::iter, +}; pub use { balance_overrides::{BalanceOverrideRequest, BalanceOverrides, BalanceOverriding}, configs::balance_overrides::Strategy, @@ -29,19 +33,35 @@ impl From for AccountOverride { } } -/// Deploys the `AnyoneAuthenticator` contract at a given address, causing it -/// to approve any solver. Pass to +/// Sets the ETH balance of an address to the given value. +pub struct EthBalanceOverride(pub U256); + +impl From for AccountOverride { + fn from(EthBalanceOverride(balance): EthBalanceOverride) -> Self { + Self { + balance: Some(balance), + ..Default::default() + } + } +} + +/// Overrides the authenticator contract's storage to allowlist a single solver +/// address. Pass to /// [`crate::simulation_builder::SimulationBuilder::state_override`] /// with the authenticator contract's address. -pub struct AnyoneAuthenticator; +pub struct SolverAllowlisting(pub Address); -impl From for AccountOverride { - fn from(_: AnyoneAuthenticator) -> Self { +impl From for AccountOverride { + fn from(SolverAllowlisting(solver): SolverAllowlisting) -> Self { + // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ + // slot_padded). + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(solver.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + let slot = keccak256(buf); Self { - code: Some( - contracts::support::AnyoneAuthenticator::AnyoneAuthenticator::DEPLOYED_BYTECODE - .clone(), - ), + state_diff: Some(iter::once((slot, B256::with_last_byte(1))).collect()), ..Default::default() } } From 258539b821b9dd08a53979e3d8bf253bfc6e52f0 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 22 Apr 2026 15:52:58 +0000 Subject: [PATCH 045/154] fixup --- crates/simulator/src/simulation_builder.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b228fcf98a..34e4053552 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -97,7 +97,7 @@ pub enum Solver { /// Configuration for wrapping the settlement in a flashloan or custom wrapper /// contract chain. pub enum WrapperConfig { - Flashloan { loans: Vec }, + Flashloan(Vec), Custom(Vec), } @@ -237,12 +237,10 @@ impl SimulationBuilder { .position(|t| *t == order.data.buy_token) .ok_or(BuildError::MissingBuyToken)?; - let executed_amount = order - .executed_amount - .unwrap_or_else(|| match order.data.kind { - OrderKind::Sell => order.data.sell_amount, - OrderKind::Buy => order.data.buy_amount, - }); + let executed_amount = order.executed_amount.unwrap_or(match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }); // Compute before clearing_prices is moved into EncodedSettlement below. let fund_amount = self @@ -295,7 +293,7 @@ impl SimulationBuilder { encode_wrapper_settlement(&wrappers, settle_calldata) .expect("wrappers is non-empty") } - Some(WrapperConfig::Flashloan { loans }) => { + Some(WrapperConfig::Flashloan(loans)) => { let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { loans: loans From 969ffee59158a5a4ccbec4540e0828ec153e46fc Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 24 Apr 2026 14:10:09 +0000 Subject: [PATCH 046/154] simulate(), handle fillable amounts --- Cargo.lock | 1 + crates/simulator/Cargo.toml | 1 + crates/simulator/src/encoding.rs | 2 +- crates/simulator/src/simulation_builder.rs | 78 +++++++++++++++++++--- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d6a24c147..e7c172064a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7774,6 +7774,7 @@ version = "0.1.0" dependencies = [ "alloy-contract", "alloy-eips", + "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", diff --git a/crates/simulator/Cargo.toml b/crates/simulator/Cargo.toml index 1b4777003b..3f5d900acd 100644 --- a/crates/simulator/Cargo.toml +++ b/crates/simulator/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] alloy-contract = { workspace = true } alloy-eips = { workspace = true } +alloy-network = { workspace = true } alloy-primitives = { workspace = true } alloy-provider = { workspace = true } alloy-rpc-types = { workspace = true } diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index d318450341..8c6d188c24 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, B256, Bytes, U256}, alloy_sol_types::SolCall, app_data::AppDataHash, - contracts::{FlashLoanRouter::LoanRequest, GPv2Settlement}, + contracts::GPv2Settlement, derive_more::Debug, model::{ interaction::InteractionData, diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 34e4053552..81ee4889f4 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -11,7 +11,9 @@ use { }, state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, }, - alloy_primitives::{Address, U256}, + alloy_network::Ethereum, + alloy_primitives::{Address, Bytes, U256}, + alloy_provider::{DynProvider, EthCall, Provider}, alloy_rpc_types::{ TransactionRequest, state::{AccountOverride, StateOverride}, @@ -20,6 +22,7 @@ use { anyhow::Result, balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, model::{ + DomainSeparator, order::{OrderData, OrderKind}, signature::{Signature, SigningScheme}, }, @@ -120,9 +123,21 @@ pub enum Prices { /// The output of [`SimulationBuilder::build`]: a transaction request and state /// overrides ready to be passed to an alloy provider for simulation. -pub struct SettlementCall { +pub struct EthCallInputs { pub request: TransactionRequest, pub state_overrides: StateOverride, + pub provider: DynProvider, +} + +impl EthCallInputs { + /// Prepares an `eth_call` with the transaction request and state overrides + /// already applied. The call is not sent — callers can chain additional + /// builder methods (e.g. `.block(...)`) before awaiting. + pub fn simulate(self) -> EthCall { + self.provider + .call(self.request) + .overrides(self.state_overrides) + } } /// Assembles a GPv2 settlement call for simulation purposes. @@ -202,7 +217,7 @@ impl SimulationBuilder { } /// Finishes the simulation struct based on the configuration thus far. - pub async fn build(self) -> Result { + pub async fn build(self) -> Result { self.build_with_modifications(|_| {}).await } @@ -212,9 +227,11 @@ impl SimulationBuilder { pub async fn build_with_modifications( self, customize: impl FnOnce(&mut EncodedSettlement), - ) -> Result { + ) -> Result { let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; + let executed_amount = self.executed_amount(order).await; + let (tokens, clearing_prices) = match self.prices { Some(Prices::Explicit { tokens, @@ -237,11 +254,6 @@ impl SimulationBuilder { .position(|t| *t == order.data.buy_token) .ok_or(BuildError::MissingBuyToken)?; - let executed_amount = order.executed_amount.unwrap_or(match order.data.kind { - OrderKind::Sell => order.data.sell_amount, - OrderKind::Buy => order.data.buy_amount, - }); - // Compute before clearing_prices is moved into EncodedSettlement below. let fund_amount = self .fund_settlement_contract @@ -344,7 +356,7 @@ impl SimulationBuilder { state_overrides.insert(address, state_override); }; - Ok(SettlementCall { + Ok(EthCallInputs { request: TransactionRequest { from: Some(from), to: Some(to.into()), @@ -352,8 +364,48 @@ impl SimulationBuilder { ..Default::default() }, state_overrides, + provider: self.simulator.0.provider.clone(), }) } + + /// Determines the amount the order is supposed to be filled with. + /// If user did not configure a value the order's remaining fillable + /// amount will be chosen (determined by call to settlement contract). + async fn executed_amount(&self, order: &Order) -> U256 { + if let Some(executed_amount) = order.executed_amount { + return executed_amount; + } + + let full = match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }; + + let uid = order + .data + .uid(&self.simulator.0.domain_separator, order.owner); + let filled_res = self + .simulator + .0 + .settlement + .filledAmount(Bytes::from(uid.0)) + .call() + .await; + + match filled_res { + Err(err) => { + // doesn't make sense to pre-emptively fail the simulation + // in this case. Most orders are not filled at all so it's + // reasonable to use the full order amount as a fallback here. + tracing::debug!( + ?err, + "failed to query executed amount - assume full order amount" + ); + full + } + Ok(filled_amount) => full.saturating_sub(filled_amount), + } + } } #[derive(Debug, thiserror::Error)] @@ -375,6 +427,8 @@ struct Inner { authenticator: Address, flash_loan_router: Address, balance_overrides: Arc, + provider: DynProvider, + domain_separator: DomainSeparator, } /// Holds the settlement contract and its authenticator address, and acts as a @@ -390,11 +444,15 @@ impl SettlementSimulator { balance_overrides: Arc, ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); + let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); + let provider = settlement.provider().clone(); Ok(Self(Arc::new(Inner { settlement, authenticator, flash_loan_router, balance_overrides, + provider, + domain_separator, }))) } From 490bcdcf316f4722b788a7cd539ddbf87629e44d Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Mon, 27 Apr 2026 10:56:47 +0100 Subject: [PATCH 047/154] Address jose review nits - today's -> current in run_eip1271_with_signature_check docstring - Add timed_simulation helper folding metrics, timeout, and outcome classification. Replaces the duplicated wrapper at both call sites. - Extract calculate_verification_gas_limit so the EIP-1271 setup no longer lives inline in validate_and_construct_order. --- crates/shared/src/order_validation.rs | 138 ++++++++++++++------------ 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 109b638797..8bc6a86f3b 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -167,17 +167,28 @@ fn classify_signature(res: &Result) -> SignatureO } } -fn classify_simulation(res: &Result<(), Eip1271SimulationError>) -> SimulationOutcome { - match res { - Ok(()) => SimulationOutcome::Pass, - Err(Eip1271SimulationError::Reverted { +/// Runs a simulation under the given timeout, recording the duration in the +/// `simulation_time` histogram, and folds the timeout / revert / infra error +/// cases into a single `SimulationOutcome`. +async fn timed_simulation( + sim: &dyn Eip1271Simulating, + order: &Order, + timeout: Duration, +) -> SimulationOutcome { + let _timer = Eip1271SimulationMetrics::get() + .simulation_time + .start_timer(); + match tokio::time::timeout(timeout, sim.simulate(order)).await { + Ok(Ok(())) => SimulationOutcome::Pass, + Ok(Err(Eip1271SimulationError::Reverted { + reason, + tenderly_url, + })) => SimulationOutcome::Fail { reason, tenderly_url, - }) => SimulationOutcome::Fail { - reason: reason.clone(), - tenderly_url: tenderly_url.clone(), }, - Err(Eip1271SimulationError::Infra(err)) => SimulationOutcome::Infra(anyhow!("{err}")), + Ok(Err(Eip1271SimulationError::Infra(err))) => SimulationOutcome::Infra(err), + Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), } } @@ -220,16 +231,7 @@ fn record_simulation_outcome( } async fn run_eip1271_simulation_only(config: &Eip1271Simulator, preview_order: &Order) { - let res = { - let _timer = Eip1271SimulationMetrics::get() - .simulation_time - .start_timer(); - tokio::time::timeout(config.timeout, config.simulator.simulate(preview_order)).await - }; - let outcome = match res { - Ok(inner) => classify_simulation(&inner), - Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), - }; + let outcome = timed_simulation(config.simulator.as_ref(), preview_order, config.timeout).await; Eip1271SimulationMetrics::get() .total .with_label_values(&[SignatureOutcome::Skipped.label(), outcome.label()]) @@ -576,6 +578,48 @@ impl OrderValidator { } } + /// Computes the `verification_gas_limit` for an order. Returns `0` for + /// non-EIP-1271 signatures, otherwise delegates to `run_eip1271_checks`. + async fn calculate_verification_gas_limit( + &self, + order: &OrderCreation, + data: &OrderData, + app_data: &OrderAppData, + domain_separator: &DomainSeparator, + owner: Address, + uid: OrderUid, + ) -> Result { + let Signature::Eip1271(signature) = &order.signature else { + return Ok(0u64); + }; + + let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); + let check = SignatureCheck::new( + owner, + hash.0, + signature.to_owned(), + app_data.interactions.pre.clone(), + app_data + .inner + .protocol + .flashloan + .as_ref() + .map(|loan| BalanceOverrideRequest { + token: loan.token, + holder: loan.receiver, + amount: loan.amount, + }), + ); + let preview_order = build_preview_order_for_sim( + data, + &app_data.interactions, + owner, + uid, + order.signature.clone(), + ); + self.run_eip1271_checks(check, &preview_order, hash).await + } + /// Entry point for the EIP-1271 block of `validate_and_construct_order`. /// /// Two paths, depending on the `eip1271_skip_creation_validation` flag: @@ -606,7 +650,7 @@ impl OrderValidator { /// configured, the full order simulation concurrently. Decides the /// outcome: /// - /// - signature `Invalid` → `InvalidEip1271Signature` (today's behaviour). + /// - signature `Invalid` → `InvalidEip1271Signature` (current behaviour). /// - signature `Other` → `ValidationError::Other` (infra error). /// - signature `Ok(gas)` + simulation `Reverted` + **Enforce** mode → /// `SimulationFailed(reason)` (the new rejection added by this PR). @@ -630,23 +674,12 @@ impl OrderValidator { }); }; - let sim = config.simulator.clone(); - let simulation_timeout = config.timeout; - let order = preview_order.clone(); - let simulation_fut = async move { - let _timer = Eip1271SimulationMetrics::get() - .simulation_time - .start_timer(); - tokio::time::timeout(simulation_timeout, sim.simulate(&order)).await - }; + let simulation_fut = + timed_simulation(config.simulator.as_ref(), preview_order, config.timeout); - let (signature_res, simulation_res) = tokio::join!(signature_fut, simulation_fut); + let (signature_res, simulation_outcome) = tokio::join!(signature_fut, simulation_fut); let signature_outcome = classify_signature(&signature_res); - let simulation_outcome = match simulation_res { - Ok(inner) => classify_simulation(&inner), - Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), - }; record_simulation_outcome( signature_outcome, &simulation_outcome, @@ -960,35 +993,16 @@ impl OrderValidating for OrderValidator { }; let uid = data.uid(domain_separator, owner); - let verification_gas_limit = - if let Signature::Eip1271(signature) = &order.signature { - let hash = hashed_eip712_message(domain_separator, &data.hash_struct()); - let check = - SignatureCheck::new( - owner, - hash.0, - signature.to_owned(), - app_data.interactions.pre.clone(), - app_data.inner.protocol.flashloan.as_ref().map(|loan| { - BalanceOverrideRequest { - token: loan.token, - holder: loan.receiver, - amount: loan.amount, - } - }), - ); - let preview_order = build_preview_order_for_sim( - &data, - &app_data.interactions, - owner, - uid, - order.signature.clone(), - ); - self.run_eip1271_checks(check, &preview_order, hash).await? - } else { - // in any other case, just apply 0 - 0u64 - }; + let verification_gas_limit = self + .calculate_verification_gas_limit( + &order, + &data, + &app_data, + domain_separator, + owner, + uid, + ) + .await?; if data.buy_amount.is_zero() || data.sell_amount.is_zero() { return Err(ValidationError::ZeroAmount); From ae91cf6f7233c27f9a0743818f83805540a63437 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Mon, 27 Apr 2026 12:11:58 +0000 Subject: [PATCH 048/154] Better handling of block target and executed amount --- crates/simulator/src/simulation_builder.rs | 98 +++++++++++++--------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 81ee4889f4..139fd502fb 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -11,6 +11,7 @@ use { }, state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, }, + alloy_eips::BlockId, alloy_network::Ethereum, alloy_primitives::{Address, Bytes, U256}, alloy_provider::{DynProvider, EthCall, Provider}, @@ -29,6 +30,19 @@ use { std::sync::Arc, }; +/// How much of an order should be filled during simulation. +pub enum ExecutionAmount { + /// Fill the full order amount (sell_amount for sell orders, buy_amount for + /// buy orders), ignoring any on-chain filled state. + Full, + /// Fill whatever is still remaining on-chain (queries the settlement + /// contract for the already-filled amount and subtracts it). Falls back to + /// the full amount if the query fails. + Remaining, + /// Use an explicit fill amount. + Explicit(U256), +} + /// A simulator-specific order that bundles the data needed to encode a trade. /// /// Construct with [`Order::new`] and add optional fields via the builder @@ -40,7 +54,7 @@ pub struct Order { signature: Signature, pre_interactions: Vec, post_interactions: Vec, - executed_amount: Option, + executed_amount: ExecutionAmount, } impl Order { @@ -51,7 +65,7 @@ impl Order { signature: Signature::default_with(SigningScheme::Eip1271), pre_interactions: vec![], post_interactions: vec![], - executed_amount: None, + executed_amount: ExecutionAmount::Remaining, } } @@ -71,8 +85,8 @@ impl Order { self } - pub fn with_executed_amount(mut self, amount: U256) -> Self { - self.executed_amount = Some(amount); + pub fn with_executed_amount(mut self, amount: ExecutionAmount) -> Self { + self.executed_amount = amount; self } } @@ -127,16 +141,18 @@ pub struct EthCallInputs { pub request: TransactionRequest, pub state_overrides: StateOverride, pub provider: DynProvider, + pub block: BlockId, } impl EthCallInputs { - /// Prepares an `eth_call` with the transaction request and state overrides - /// already applied. The call is not sent — callers can chain additional - /// builder methods (e.g. `.block(...)`) before awaiting. + /// Prepares an `eth_call` with the transaction request, state overrides, + /// and block already applied. The call is not sent — callers can chain + /// additional builder methods before awaiting. pub fn simulate(self) -> EthCall { self.provider .call(self.request) .overrides(self.state_overrides) + .block(self.block) } } @@ -155,6 +171,7 @@ pub struct SimulationBuilder { state_overrides: StateOverride, simulator: SettlementSimulator, fund_settlement_contract: bool, + block: BlockId, } impl SimulationBuilder { @@ -208,6 +225,11 @@ impl SimulationBuilder { self } + pub fn at_block(mut self, block: BlockId) -> Self { + self.block = block; + self + } + /// Override the settlement contract's buy token balance so it can pay out /// the order without any external liquidity. The required amount is derived /// from the order's executed amount and clearing prices at `build()` time. @@ -230,7 +252,7 @@ impl SimulationBuilder { ) -> Result { let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; - let executed_amount = self.executed_amount(order).await; + let executed_amount = self.executed_amount(order).await?; let (tokens, clearing_prices) = match self.prices { Some(Prices::Explicit { @@ -239,10 +261,13 @@ impl SimulationBuilder { }) => (tokens, clearing_prices), // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. - _ => ( + Some(Prices::Limit) => ( vec![order.data.sell_token, order.data.buy_token], vec![order.data.buy_amount, order.data.sell_amount], ), + None => { + return Err(BuildError::NoPriceEncoding); + } }; let sell_token_index = tokens @@ -365,46 +390,36 @@ impl SimulationBuilder { }, state_overrides, provider: self.simulator.0.provider.clone(), + block: self.block, }) } /// Determines the amount the order is supposed to be filled with. - /// If user did not configure a value the order's remaining fillable - /// amount will be chosen (determined by call to settlement contract). - async fn executed_amount(&self, order: &Order) -> U256 { - if let Some(executed_amount) = order.executed_amount { - return executed_amount; - } - + async fn executed_amount(&self, order: &Order) -> Result { let full = match order.data.kind { OrderKind::Sell => order.data.sell_amount, OrderKind::Buy => order.data.buy_amount, }; - let uid = order - .data - .uid(&self.simulator.0.domain_separator, order.owner); - let filled_res = self - .simulator - .0 - .settlement - .filledAmount(Bytes::from(uid.0)) - .call() - .await; - - match filled_res { - Err(err) => { - // doesn't make sense to pre-emptively fail the simulation - // in this case. Most orders are not filled at all so it's - // reasonable to use the full order amount as a fallback here. - tracing::debug!( - ?err, - "failed to query executed amount - assume full order amount" - ); - full + Ok(match order.executed_amount { + ExecutionAmount::Full => full, + ExecutionAmount::Explicit(amount) => amount, + ExecutionAmount::Remaining => { + let uid = order + .data + .uid(&self.simulator.0.domain_separator, order.owner); + let filled_amount = self + .simulator + .0 + .settlement + .filledAmount(Bytes::from(uid.0)) + .block(self.block) + .call() + .await + .map_err(|err| BuildError::FilledAmountQuery(err.into()))?; + full.saturating_sub(filled_amount) } - Ok(filled_amount) => full.saturating_sub(filled_amount), - } + }) } } @@ -420,6 +435,10 @@ pub enum BuildError { MissingBuyToken, #[error("could not override token balances to fund settlement contract")] FailedToOverrideBalances, + #[error("no strategy to compute the price vector was chosen")] + NoPriceEncoding, + #[error("failed to query filled amount from settlement contract: {0}")] + FilledAmountQuery(#[source] anyhow::Error), } struct Inner { @@ -469,6 +488,7 @@ impl SettlementSimulator { auction_id: None, state_overrides: StateOverride::default(), fund_settlement_contract: false, + block: BlockId::latest(), } } } From 2ccedbb8900d9fbdae1e8c197bc7c2f034536949 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Mon, 27 Apr 2026 12:24:59 +0000 Subject: [PATCH 049/154] Move encoding logic into separate file --- crates/simulator/src/lib.rs | 1 + crates/simulator/src/simulation_builder.rs | 245 +++----------------- crates/simulator/src/simulation_encoding.rs | 199 ++++++++++++++++ 3 files changed, 233 insertions(+), 212 deletions(-) create mode 100644 crates/simulator/src/simulation_encoding.rs diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index 83f251e223..a0d66dd65f 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -1,6 +1,7 @@ pub mod encoding; pub mod ethereum; pub mod simulation_builder; +mod simulation_encoding; pub mod state_override_helpers; pub mod swap_simulator; pub mod tenderly; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 139fd502fb..84a0b81d91 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,16 +1,5 @@ use { - crate::{ - encoding::{ - EncodedSettlement, - Interaction, - Interactions, - WrapperCall, - encode_interactions, - encode_trade, - encode_wrapper_settlement, - }, - state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, - }, + crate::encoding::{EncodedSettlement, Interaction, WrapperCall}, alloy_eips::BlockId, alloy_network::Ethereum, alloy_primitives::{Address, Bytes, U256}, @@ -19,12 +8,11 @@ use { TransactionRequest, state::{AccountOverride, StateOverride}, }, - alloy_sol_types::SolCall, anyhow::Result, - balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, + balance_overrides::BalanceOverriding, model::{ DomainSeparator, - order::{OrderData, OrderKind}, + order::OrderData, signature::{Signature, SigningScheme}, }, std::sync::Arc, @@ -49,12 +37,12 @@ pub enum ExecutionAmount { /// methods. Defaults to an EIP-1271 signature (pairs with [`FakeUser`] for /// simulations that need to bypass signature verification). pub struct Order { - data: OrderData, - owner: Address, - signature: Signature, - pre_interactions: Vec, - post_interactions: Vec, - executed_amount: ExecutionAmount, + pub(crate) data: OrderData, + pub(crate) owner: Address, + pub(crate) signature: Signature, + pub(crate) pre_interactions: Vec, + pub(crate) post_interactions: Vec, + pub(crate) executed_amount: ExecutionAmount, } impl Order { @@ -160,18 +148,18 @@ impl EthCallInputs { /// /// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. pub struct SimulationBuilder { - order: Option, - pre_interactions: Vec, - main_interactions: Vec, - post_interactions: Vec, - wrapper: Option, - prices: Option, - solver: Option, - auction_id: Option, - state_overrides: StateOverride, - simulator: SettlementSimulator, - fund_settlement_contract: bool, - block: BlockId, + pub(crate) order: Option, + pub(crate) pre_interactions: Vec, + pub(crate) main_interactions: Vec, + pub(crate) post_interactions: Vec, + pub(crate) wrapper: Option, + pub(crate) prices: Option, + pub(crate) solver: Option, + pub(crate) auction_id: Option, + pub(crate) state_overrides: StateOverride, + pub(crate) simulator: SettlementSimulator, + pub(crate) fund_settlement_contract: bool, + pub(crate) block: BlockId, } impl SimulationBuilder { @@ -244,182 +232,15 @@ impl SimulationBuilder { } /// Same as `build()` but allows the caller to alter the simulation - /// before it gets finalized. This should only be used for very speicific + /// before it gets finalized. This should only be used for very specific /// setups. pub async fn build_with_modifications( self, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { - let order = self.order.as_ref().ok_or(BuildError::NoOrder)?; - - let executed_amount = self.executed_amount(order).await?; - - let (tokens, clearing_prices) = match self.prices { - Some(Prices::Explicit { - tokens, - clearing_prices, - }) => (tokens, clearing_prices), - // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. - // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. - Some(Prices::Limit) => ( - vec![order.data.sell_token, order.data.buy_token], - vec![order.data.buy_amount, order.data.sell_amount], - ), - None => { - return Err(BuildError::NoPriceEncoding); - } - }; - - let sell_token_index = tokens - .iter() - .position(|t| *t == order.data.sell_token) - .ok_or(BuildError::MissingSellToken)?; - let buy_token_index = tokens - .iter() - .position(|t| *t == order.data.buy_token) - .ok_or(BuildError::MissingBuyToken)?; - - // Compute before clearing_prices is moved into EncodedSettlement below. - let fund_amount = self - .fund_settlement_contract - .then(|| match order.data.kind { - OrderKind::Sell => clearing_prices[sell_token_index] - .saturating_mul(executed_amount) - .checked_div(clearing_prices[buy_token_index]) - .unwrap_or(U256::MAX), - OrderKind::Buy => executed_amount, - }); - - let trade = encode_trade( - &order.data, - &order.signature, - order.owner, - sell_token_index, - buy_token_index, - executed_amount, - ); - - let order_pre = &order.pre_interactions; - let order_post = &order.post_interactions; - - let mut settlement = EncodedSettlement { - tokens, - clearing_prices, - trades: vec![trade], - interactions: Interactions { - // order's pre-hooks run before any additional pre-interactions - pre: encode_interactions(order_pre.iter().chain(&self.pre_interactions)), - main: encode_interactions(&self.main_interactions), - // additional post-interactions run before the order's post-hooks - post: encode_interactions(self.post_interactions.iter().chain(order_post)), - }, - }; - - customize(&mut settlement); - - let settle_calldata = { - let mut bytes = settlement.into_settle_call().to_vec(); - if let Some(id) = self.auction_id { - bytes.extend_from_slice(&id.to_be_bytes()); - } - bytes.into() - }; - - let (to, input) = match self.wrapper { - Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { - encode_wrapper_settlement(&wrappers, settle_calldata) - .expect("wrappers is non-empty") - } - Some(WrapperConfig::Flashloan(loans)) => { - let calldata = - contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { - loans: loans - .into_iter() - .map(|l| contracts::FlashLoanRouter::LoanRequest::Data { - amount: l.amount, - borrower: l.borrower, - lender: l.lender, - token: l.token, - }) - .collect(), - settlement: settle_calldata, - } - .abi_encode() - .into(); - (self.simulator.0.flash_loan_router, calldata) - } - _ => (*self.simulator.0.settlement.address(), settle_calldata), - }; - - let mut state_overrides = self.state_overrides; - let from = match self.solver { - Some(Solver::Real(addr)) => addr, - Some(Solver::Fake(opt)) => { - let addr = opt.unwrap_or_else(Address::random); - state_overrides.insert(addr, EthBalanceOverride(U256::MAX / U256::from(2)).into()); - - state_overrides.insert( - self.simulator.0.authenticator, - SolverAllowlisting(addr).into(), - ); - addr - } - None => return Err(BuildError::NoSolver), - }; - if let Some(amount) = fund_amount { - let (address, state_override) = self - .simulator - .0 - .balance_overrides - .state_override(BalanceOverrideRequest { - token: order.data.buy_token, - holder: *self.simulator.0.settlement.address(), - amount, - }) - .await - .ok_or(BuildError::FailedToOverrideBalances)?; - state_overrides.insert(address, state_override); - }; - - Ok(EthCallInputs { - request: TransactionRequest { - from: Some(from), - to: Some(to.into()), - input: input.into(), - ..Default::default() - }, - state_overrides, - provider: self.simulator.0.provider.clone(), - block: self.block, - }) - } - - /// Determines the amount the order is supposed to be filled with. - async fn executed_amount(&self, order: &Order) -> Result { - let full = match order.data.kind { - OrderKind::Sell => order.data.sell_amount, - OrderKind::Buy => order.data.buy_amount, - }; - - Ok(match order.executed_amount { - ExecutionAmount::Full => full, - ExecutionAmount::Explicit(amount) => amount, - ExecutionAmount::Remaining => { - let uid = order - .data - .uid(&self.simulator.0.domain_separator, order.owner); - let filled_amount = self - .simulator - .0 - .settlement - .filledAmount(Bytes::from(uid.0)) - .block(self.block) - .call() - .await - .map_err(|err| BuildError::FilledAmountQuery(err.into()))?; - full.saturating_sub(filled_amount) - } - }) + // Forward to a helper function to split the boring repetitive builder + // code from the non-trivial code that actually does the encoding. + crate::simulation_encoding::encode(self, customize).await } } @@ -441,20 +262,20 @@ pub enum BuildError { FilledAmountQuery(#[source] anyhow::Error), } -struct Inner { - settlement: contracts::GPv2Settlement::Instance, - authenticator: Address, - flash_loan_router: Address, - balance_overrides: Arc, - provider: DynProvider, - domain_separator: DomainSeparator, +pub(crate) struct Inner { + pub(crate) settlement: contracts::GPv2Settlement::Instance, + pub(crate) authenticator: Address, + pub(crate) flash_loan_router: Address, + pub(crate) balance_overrides: Arc, + pub(crate) provider: DynProvider, + pub(crate) domain_separator: DomainSeparator, } /// Holds the settlement contract and its authenticator address, and acts as a /// factory for [`SimulationBuilder`] instances that are pre-configured with /// these values. #[derive(Clone)] -pub struct SettlementSimulator(Arc); +pub struct SettlementSimulator(pub(crate) Arc); impl SettlementSimulator { pub async fn new( diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs new file mode 100644 index 0000000000..d3d8c2f916 --- /dev/null +++ b/crates/simulator/src/simulation_encoding.rs @@ -0,0 +1,199 @@ +use { + crate::{ + encoding::{ + EncodedSettlement, + Interactions, + encode_interactions, + encode_trade, + encode_wrapper_settlement, + }, + simulation_builder::{ + BuildError, + EthCallInputs, + ExecutionAmount, + Order, + Prices, + SimulationBuilder, + Solver, + WrapperConfig, + }, + state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, + }, + alloy_primitives::{Address, Bytes, U256}, + alloy_rpc_types::TransactionRequest, + alloy_sol_types::SolCall, + balance_overrides::BalanceOverrideRequest, + model::order::OrderKind, +}; + +pub(crate) async fn encode( + builder: SimulationBuilder, + customize: impl FnOnce(&mut EncodedSettlement), +) -> Result { + let order = builder.order.as_ref().ok_or(BuildError::NoOrder)?; + + let executed_amount = executed_amount(&builder, order).await?; + + let (tokens, clearing_prices) = match builder.prices { + Some(Prices::Explicit { + tokens, + clearing_prices, + }) => (tokens, clearing_prices), + // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. + // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. + Some(Prices::Limit) => ( + vec![order.data.sell_token, order.data.buy_token], + vec![order.data.buy_amount, order.data.sell_amount], + ), + None => { + return Err(BuildError::NoPriceEncoding); + } + }; + + let sell_token_index = tokens + .iter() + .position(|t| *t == order.data.sell_token) + .ok_or(BuildError::MissingSellToken)?; + let buy_token_index = tokens + .iter() + .position(|t| *t == order.data.buy_token) + .ok_or(BuildError::MissingBuyToken)?; + + // Compute before clearing_prices is moved into EncodedSettlement below. + let fund_amount = builder + .fund_settlement_contract + .then(|| match order.data.kind { + OrderKind::Sell => clearing_prices[sell_token_index] + .saturating_mul(executed_amount) + .checked_div(clearing_prices[buy_token_index]) + .unwrap_or(U256::MAX), + OrderKind::Buy => executed_amount, + }); + + let trade = encode_trade( + &order.data, + &order.signature, + order.owner, + sell_token_index, + buy_token_index, + executed_amount, + ); + + let order_pre = &order.pre_interactions; + let order_post = &order.post_interactions; + + let mut settlement = EncodedSettlement { + tokens, + clearing_prices, + trades: vec![trade], + interactions: Interactions { + // order's pre-hooks run before any additional pre-interactions + pre: encode_interactions(order_pre.iter().chain(&builder.pre_interactions)), + main: encode_interactions(&builder.main_interactions), + // additional post-interactions run before the order's post-hooks + post: encode_interactions(builder.post_interactions.iter().chain(order_post)), + }, + }; + + customize(&mut settlement); + + let settle_calldata = { + let mut bytes = settlement.into_settle_call().to_vec(); + if let Some(id) = builder.auction_id { + bytes.extend_from_slice(&id.to_be_bytes()); + } + bytes.into() + }; + + let (to, input) = match builder.wrapper { + Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { + encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") + } + Some(WrapperConfig::Flashloan(loans)) => { + let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { + loans: loans + .into_iter() + .map(|l| contracts::FlashLoanRouter::LoanRequest::Data { + amount: l.amount, + borrower: l.borrower, + lender: l.lender, + token: l.token, + }) + .collect(), + settlement: settle_calldata, + } + .abi_encode() + .into(); + (builder.simulator.0.flash_loan_router, calldata) + } + _ => (*builder.simulator.0.settlement.address(), settle_calldata), + }; + + let mut state_overrides = builder.state_overrides; + let from = match builder.solver { + Some(Solver::Real(addr)) => addr, + Some(Solver::Fake(opt)) => { + let addr = opt.unwrap_or_else(Address::random); + state_overrides.insert(addr, EthBalanceOverride(U256::MAX / U256::from(2)).into()); + state_overrides.insert( + builder.simulator.0.authenticator, + SolverAllowlisting(addr).into(), + ); + addr + } + None => return Err(BuildError::NoSolver), + }; + if let Some(amount) = fund_amount { + let (address, state_override) = builder + .simulator + .0 + .balance_overrides + .state_override(BalanceOverrideRequest { + token: order.data.buy_token, + holder: *builder.simulator.0.settlement.address(), + amount, + }) + .await + .ok_or(BuildError::FailedToOverrideBalances)?; + state_overrides.insert(address, state_override); + } + + Ok(EthCallInputs { + request: TransactionRequest { + from: Some(from), + to: Some(to.into()), + input: input.into(), + ..Default::default() + }, + state_overrides, + provider: builder.simulator.0.provider.clone(), + block: builder.block, + }) +} + +async fn executed_amount(builder: &SimulationBuilder, order: &Order) -> Result { + let full = match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }; + + Ok(match order.executed_amount { + ExecutionAmount::Full => full, + ExecutionAmount::Explicit(amount) => amount, + ExecutionAmount::Remaining => { + let uid = order + .data + .uid(&builder.simulator.0.domain_separator, order.owner); + let filled_amount = builder + .simulator + .0 + .settlement + .filledAmount(Bytes::from(uid.0)) + .block(builder.block) + .call() + .await + .map_err(|err| BuildError::FilledAmountQuery(err.into()))?; + full.saturating_sub(filled_amount) + } + }) +} From c1a0fe2e24edf6747c54d4a8115926af9dc6cf43 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Mon, 27 Apr 2026 13:38:15 +0000 Subject: [PATCH 050/154] move code around --- crates/simulator/src/simulation_builder.rs | 340 +++++++++++---------- 1 file changed, 171 insertions(+), 169 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 84a0b81d91..b5b941ac5f 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -18,130 +18,56 @@ use { std::sync::Arc, }; -/// How much of an order should be filled during simulation. -pub enum ExecutionAmount { - /// Fill the full order amount (sell_amount for sell orders, buy_amount for - /// buy orders), ignoring any on-chain filled state. - Full, - /// Fill whatever is still remaining on-chain (queries the settlement - /// contract for the already-filled amount and subtracts it). Falls back to - /// the full amount if the query fails. - Remaining, - /// Use an explicit fill amount. - Explicit(U256), -} +/// Holds the settlement contract and its authenticator address, and acts as a +/// factory for [`SimulationBuilder`] instances that are pre-configured with +/// these values. +#[derive(Clone)] +pub struct SettlementSimulator(pub(crate) Arc); -/// A simulator-specific order that bundles the data needed to encode a trade. -/// -/// Construct with [`Order::new`] and add optional fields via the builder -/// methods. Defaults to an EIP-1271 signature (pairs with [`FakeUser`] for -/// simulations that need to bypass signature verification). -pub struct Order { - pub(crate) data: OrderData, - pub(crate) owner: Address, - pub(crate) signature: Signature, - pub(crate) pre_interactions: Vec, - pub(crate) post_interactions: Vec, - pub(crate) executed_amount: ExecutionAmount, +pub(crate) struct Inner { + pub(crate) settlement: contracts::GPv2Settlement::Instance, + pub(crate) authenticator: Address, + pub(crate) flash_loan_router: Address, + pub(crate) balance_overrides: Arc, + pub(crate) provider: DynProvider, + pub(crate) domain_separator: DomainSeparator, } -impl Order { - pub fn new(data: OrderData) -> Self { - Self { - data, - owner: Address::ZERO, - signature: Signature::default_with(SigningScheme::Eip1271), +impl SettlementSimulator { + pub async fn new( + settlement: contracts::GPv2Settlement::Instance, + flash_loan_router: Address, + balance_overrides: Arc, + ) -> Result { + let authenticator = Address(settlement.authenticator().call().await?.0); + let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); + let provider = settlement.provider().clone(); + Ok(Self(Arc::new(Inner { + settlement, + authenticator, + flash_loan_router, + balance_overrides, + provider, + domain_separator, + }))) + } + + pub fn new_simulation_builder(&self) -> SimulationBuilder { + SimulationBuilder { + simulator: self.clone(), + order: None, pre_interactions: vec![], + main_interactions: vec![], post_interactions: vec![], - executed_amount: ExecutionAmount::Remaining, + wrapper: None, + prices: None, + solver: None, + auction_id: None, + state_overrides: StateOverride::default(), + fund_settlement_contract: false, + block: BlockId::latest(), } } - - pub fn with_signature(mut self, owner: Address, signature: Signature) -> Self { - self.owner = owner; - self.signature = signature; - self - } - - pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { - self.pre_interactions = interactions; - self - } - - pub fn with_post_interactions(mut self, interactions: Vec) -> Self { - self.post_interactions = interactions; - self - } - - pub fn with_executed_amount(mut self, amount: ExecutionAmount) -> Self { - self.executed_amount = amount; - self - } -} - -pub struct FlashloanRequest { - pub amount: U256, - pub borrower: Address, - pub lender: Address, - pub token: Address, -} - -pub enum Solver { - /// Simulation assumes this is an actual solver so no state overrides will - /// be applied to allow list it explicitly. - /// If you need a very specific solver setup for your simulation consider - /// using this and explicitly add the necessary state overrides yourself - /// with `Simulation::build_with_modifications()`. - Real(Address), - /// A fake solver for simulation. Uses the provided address or generates a - /// random one. The simulation builder will automatically set the required - /// state overrides to give it enough ETH and allow list it as a solver. - Fake(Option
), -} - -/// Configuration for wrapping the settlement in a flashloan or custom wrapper -/// contract chain. -pub enum WrapperConfig { - Flashloan(Vec), - Custom(Vec), -} - -/// How clearing prices are determined for the encoded settlement. -pub enum Prices { - /// Derive clearing prices directly from the order's limit price. - /// - /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = - /// sell_amount`, exactly satisfying the order's limit with no surplus. - /// This should NOT be used when encoding solutions you actually want - /// to submit. - Limit, - // TODO: check how this can be made nicer. - /// Explicit token list and matching clearing prices. - Explicit { - tokens: Vec
, - clearing_prices: Vec, - }, -} - -/// The output of [`SimulationBuilder::build`]: a transaction request and state -/// overrides ready to be passed to an alloy provider for simulation. -pub struct EthCallInputs { - pub request: TransactionRequest, - pub state_overrides: StateOverride, - pub provider: DynProvider, - pub block: BlockId, -} - -impl EthCallInputs { - /// Prepares an `eth_call` with the transaction request, state overrides, - /// and block already applied. The call is not sent — callers can chain - /// additional builder methods before awaiting. - pub fn simulate(self) -> EthCall { - self.provider - .call(self.request) - .overrides(self.state_overrides) - .block(self.block) - } } /// Assembles a GPv2 settlement call for simulation purposes. @@ -163,6 +89,8 @@ pub struct SimulationBuilder { } impl SimulationBuilder { + // TODO: support multiple orders to support use case of encoding solutions + // in the driver and the trade verification (requires JIT orders) pub fn add_order(mut self, order: Order) -> Self { self.order = Some(order); self @@ -244,6 +172,132 @@ impl SimulationBuilder { } } +pub enum Solver { + /// Simulation assumes this is an actual solver so no state overrides will + /// be applied to allow list it explicitly. + /// If you need a very specific solver setup for your simulation consider + /// using this and explicitly add the necessary state overrides yourself + /// with `Simulation::build_with_modifications()`. + Real(Address), + /// A fake solver for simulation. Uses the provided address or generates a + /// random one. The simulation builder will automatically set the required + /// state overrides to give it enough ETH and allow list it as a solver. + Fake(Option
), +} + +/// How clearing prices are determined for the encoded settlement. +pub enum Prices { + /// Derive clearing prices directly from the order's limit price. + /// + /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = + /// sell_amount`, exactly satisfying the order's limit with no surplus. + /// This should NOT be used when encoding solutions you actually want + /// to submit. + Limit, + // TODO: check how this can be made nicer. + /// Explicit token list and matching clearing prices. + Explicit { + tokens: Vec
, + clearing_prices: Vec, + }, +} + +/// How much of an order should be filled during simulation. +pub enum ExecutionAmount { + /// Fill the full order amount (sell_amount for sell orders, buy_amount for + /// buy orders), ignoring any on-chain filled state. + Full, + /// Fill whatever is still remaining on-chain (queries the settlement + /// contract for the already-filled amount and subtracts it). Falls back to + /// the full amount if the query fails. + Remaining, + /// Use an explicit fill amount. + Explicit(U256), +} + +/// A simulator-specific order that bundles the data needed to encode a trade. +/// +/// Construct with [`Order::new`] and add optional fields via the builder +/// methods. Defaults to an EIP-1271 signature (pairs with [`FakeUser`] for +/// simulations that need to bypass signature verification). +pub struct Order { + pub(crate) data: OrderData, + pub(crate) owner: Address, + pub(crate) signature: Signature, + pub(crate) pre_interactions: Vec, + pub(crate) post_interactions: Vec, + pub(crate) executed_amount: ExecutionAmount, +} + +/// Configuration for wrapping the settlement in a flashloan or custom wrapper +/// contract chain. +pub enum WrapperConfig { + Flashloan(Vec), + Custom(Vec), +} + +pub struct FlashloanRequest { + pub amount: U256, + pub borrower: Address, + pub lender: Address, + pub token: Address, +} + +impl Order { + pub fn new(data: OrderData) -> Self { + Self { + data, + owner: Address::ZERO, + signature: Signature::default_with(SigningScheme::Eip1271), + pre_interactions: vec![], + post_interactions: vec![], + executed_amount: ExecutionAmount::Remaining, + } + } + + pub fn with_signature(mut self, owner: Address, signature: Signature) -> Self { + self.owner = owner; + self.signature = signature; + self + } + + pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { + self.pre_interactions = interactions; + self + } + + pub fn with_post_interactions(mut self, interactions: Vec) -> Self { + self.post_interactions = interactions; + self + } + + pub fn with_executed_amount(mut self, amount: ExecutionAmount) -> Self { + self.executed_amount = amount; + self + } +} + +/// The output of [`SimulationBuilder::build`]: a transaction request and state +/// overrides ready to be passed to an alloy provider for simulation. +pub struct EthCallInputs { + pub request: TransactionRequest, + pub state_overrides: StateOverride, + pub provider: DynProvider, + pub block: BlockId, +} + +impl EthCallInputs { + /// Prepares an `eth_call` with the transaction request, state overrides, + /// and block already applied. The call is not sent — callers can chain + /// additional builder methods before awaiting. + pub fn simulate(self) -> EthCall { + self.provider + .call(self.request) + .overrides(self.state_overrides) + .block(self.block) + } +} + #[derive(Debug, thiserror::Error)] pub enum BuildError { #[error("no order was added")] @@ -261,55 +315,3 @@ pub enum BuildError { #[error("failed to query filled amount from settlement contract: {0}")] FilledAmountQuery(#[source] anyhow::Error), } - -pub(crate) struct Inner { - pub(crate) settlement: contracts::GPv2Settlement::Instance, - pub(crate) authenticator: Address, - pub(crate) flash_loan_router: Address, - pub(crate) balance_overrides: Arc, - pub(crate) provider: DynProvider, - pub(crate) domain_separator: DomainSeparator, -} - -/// Holds the settlement contract and its authenticator address, and acts as a -/// factory for [`SimulationBuilder`] instances that are pre-configured with -/// these values. -#[derive(Clone)] -pub struct SettlementSimulator(pub(crate) Arc); - -impl SettlementSimulator { - pub async fn new( - settlement: contracts::GPv2Settlement::Instance, - flash_loan_router: Address, - balance_overrides: Arc, - ) -> Result { - let authenticator = Address(settlement.authenticator().call().await?.0); - let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); - let provider = settlement.provider().clone(); - Ok(Self(Arc::new(Inner { - settlement, - authenticator, - flash_loan_router, - balance_overrides, - provider, - domain_separator, - }))) - } - - pub fn new_simulation_builder(&self) -> SimulationBuilder { - SimulationBuilder { - simulator: self.clone(), - order: None, - pre_interactions: vec![], - main_interactions: vec![], - post_interactions: vec![], - wrapper: None, - prices: None, - solver: None, - auction_id: None, - state_overrides: StateOverride::default(), - fund_settlement_contract: false, - block: BlockId::latest(), - } - } -} From 36d46aa75067a8b6f9bd3c7c312f0e865929c739 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Mon, 27 Apr 2026 14:41:27 +0000 Subject: [PATCH 051/154] store chain_id and convert to tenderly request --- crates/simulator/src/simulation_builder.rs | 10 ++++++++-- crates/simulator/src/simulation_encoding.rs | 2 +- crates/simulator/src/tenderly/mod.rs | 16 +++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b5b941ac5f..2502426746 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -31,6 +31,7 @@ pub(crate) struct Inner { pub(crate) balance_overrides: Arc, pub(crate) provider: DynProvider, pub(crate) domain_separator: DomainSeparator, + pub(crate) chain_id: u64, } impl SettlementSimulator { @@ -42,6 +43,7 @@ impl SettlementSimulator { let authenticator = Address(settlement.authenticator().call().await?.0); let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); let provider = settlement.provider().clone(); + let chain_id = provider.get_chain_id().await?; Ok(Self(Arc::new(Inner { settlement, authenticator, @@ -49,6 +51,7 @@ impl SettlementSimulator { balance_overrides, provider, domain_separator, + chain_id, }))) } @@ -282,7 +285,7 @@ impl Order { pub struct EthCallInputs { pub request: TransactionRequest, pub state_overrides: StateOverride, - pub provider: DynProvider, + pub simulator: SettlementSimulator, pub block: BlockId, } @@ -291,7 +294,10 @@ impl EthCallInputs { /// and block already applied. The call is not sent — callers can chain /// additional builder methods before awaiting. pub fn simulate(self) -> EthCall { - self.provider + self.simulator + .0 + .provider + .clone() .call(self.request) .overrides(self.state_overrides) .block(self.block) diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index d3d8c2f916..e176f603d4 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -166,7 +166,7 @@ pub(crate) async fn encode( ..Default::default() }, state_overrides, - provider: builder.simulator.0.provider.clone(), + simulator: builder.simulator, block: builder.block, }) } diff --git a/crates/simulator/src/tenderly/mod.rs b/crates/simulator/src/tenderly/mod.rs index fe6c55d66d..9c544479cd 100644 --- a/crates/simulator/src/tenderly/mod.rs +++ b/crates/simulator/src/tenderly/mod.rs @@ -1,5 +1,6 @@ use { - crate::ethereum::Ethereum, + crate::{ethereum::Ethereum, simulation_builder::EthCallInputs}, + alloy_eips::{BlockId, BlockNumberOrTag}, alloy_primitives::TxKind, alloy_rpc_types::{TransactionRequest, state::StateOverride}, anyhow::{Result, anyhow}, @@ -245,6 +246,19 @@ pub fn prepare_request( }) } +pub fn request_from_eth_call(inputs: &EthCallInputs) -> Result { + let block = match inputs.block { + BlockId::Number(BlockNumberOrTag::Number(n)) => Some(BlockNo(n)), + _ => None, + }; + prepare_request( + inputs.simulator.0.chain_id.to_string(), + &inputs.request, + inputs.state_overrides.clone(), + block, + ) +} + pub fn log_simulation_request( simulation_endpoint: &Url, dashboard: &Url, From 9259b06e5c6c781bb25ec9e2888622be1b43d05a Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Mon, 27 Apr 2026 15:20:50 +0000 Subject: [PATCH 052/154] current block watcher in simulator --- crates/simulator/src/simulation_builder.rs | 23 +++++++++++++++------ crates/simulator/src/simulation_encoding.rs | 18 ++++++++++++---- crates/simulator/src/tenderly/mod.rs | 7 +------ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 2502426746..5cdfa209aa 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,6 +1,5 @@ use { crate::encoding::{EncodedSettlement, Interaction, WrapperCall}, - alloy_eips::BlockId, alloy_network::Ethereum, alloy_primitives::{Address, Bytes, U256}, alloy_provider::{DynProvider, EthCall, Provider}, @@ -10,6 +9,7 @@ use { }, anyhow::Result, balance_overrides::BalanceOverriding, + ethrpc::block_stream::CurrentBlockWatcher, model::{ DomainSeparator, order::OrderData, @@ -32,6 +32,7 @@ pub(crate) struct Inner { pub(crate) provider: DynProvider, pub(crate) domain_separator: DomainSeparator, pub(crate) chain_id: u64, + pub(crate) current_block: CurrentBlockWatcher, } impl SettlementSimulator { @@ -39,6 +40,7 @@ impl SettlementSimulator { settlement: contracts::GPv2Settlement::Instance, flash_loan_router: Address, balance_overrides: Arc, + current_block: CurrentBlockWatcher, ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); @@ -52,6 +54,7 @@ impl SettlementSimulator { provider, domain_separator, chain_id, + current_block, }))) } @@ -68,11 +71,19 @@ impl SettlementSimulator { auction_id: None, state_overrides: StateOverride::default(), fund_settlement_contract: false, - block: BlockId::latest(), + block: Block::Latest, } } } +/// Which block to simulate against. +pub enum Block { + /// Use the current head block from the block stream, pinning the + /// simulation to a concrete number at build time. + Latest, + Number(u64), +} + /// Assembles a GPv2 settlement call for simulation purposes. /// /// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. @@ -88,7 +99,7 @@ pub struct SimulationBuilder { pub(crate) state_overrides: StateOverride, pub(crate) simulator: SettlementSimulator, pub(crate) fund_settlement_contract: bool, - pub(crate) block: BlockId, + pub(crate) block: Block, } impl SimulationBuilder { @@ -144,7 +155,7 @@ impl SimulationBuilder { self } - pub fn at_block(mut self, block: BlockId) -> Self { + pub fn at_block(mut self, block: Block) -> Self { self.block = block; self } @@ -286,7 +297,7 @@ pub struct EthCallInputs { pub request: TransactionRequest, pub state_overrides: StateOverride, pub simulator: SettlementSimulator, - pub block: BlockId, + pub block: u64, } impl EthCallInputs { @@ -300,7 +311,7 @@ impl EthCallInputs { .clone() .call(self.request) .overrides(self.state_overrides) - .block(self.block) + .block(self.block.into()) } } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index e176f603d4..1d0dc19082 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -8,6 +8,7 @@ use { encode_wrapper_settlement, }, simulation_builder::{ + Block, BuildError, EthCallInputs, ExecutionAmount, @@ -32,7 +33,12 @@ pub(crate) async fn encode( ) -> Result { let order = builder.order.as_ref().ok_or(BuildError::NoOrder)?; - let executed_amount = executed_amount(&builder, order).await?; + let block = match builder.block { + Block::Latest => builder.simulator.0.current_block.borrow().number, + Block::Number(n) => n, + }; + + let executed_amount = executed_amount(&builder, order, block).await?; let (tokens, clearing_prices) = match builder.prices { Some(Prices::Explicit { @@ -166,12 +172,16 @@ pub(crate) async fn encode( ..Default::default() }, state_overrides, + block, simulator: builder.simulator, - block: builder.block, }) } -async fn executed_amount(builder: &SimulationBuilder, order: &Order) -> Result { +async fn executed_amount( + builder: &SimulationBuilder, + order: &Order, + block: u64, +) -> Result { let full = match order.data.kind { OrderKind::Sell => order.data.sell_amount, OrderKind::Buy => order.data.buy_amount, @@ -189,7 +199,7 @@ async fn executed_amount(builder: &SimulationBuilder, order: &Order) -> Result Result { - let block = match inputs.block { - BlockId::Number(BlockNumberOrTag::Number(n)) => Some(BlockNo(n)), - _ => None, - }; prepare_request( inputs.simulator.0.chain_id.to_string(), &inputs.request, inputs.state_overrides.clone(), - block, + Some(BlockNo(inputs.block)), ) } From fb58a25d77126e25b44b4f25f1739499adb049b4 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 28 Apr 2026 07:41:54 +0000 Subject: [PATCH 053/154] more fixes --- crates/orderbook/src/orderbook.rs | 46 +++++--- crates/orderbook/src/run.rs | 27 ++++- crates/simulator/src/simulation_builder.rs | 112 ++++++++++++++++++-- crates/simulator/src/simulation_encoding.rs | 10 +- crates/simulator/src/tenderly/mod.rs | 11 +- 5 files changed, 170 insertions(+), 36 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 2058f8a91c..ba594d3202 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -42,6 +42,7 @@ use { is_order_outside_market_price, }, }, + simulator::simulation_builder::SettlementSimulator, std::{borrow::Cow, sync::Arc}, strum::Display, thiserror::Error, @@ -240,6 +241,7 @@ pub struct Orderbook { app_data: Arc, active_order_competition_threshold: u32, order_simulator: Option>, + order_simulator2: Option>, } impl Orderbook { @@ -253,6 +255,7 @@ impl Orderbook { app_data: Arc, active_order_competition_threshold: u32, order_simulator: Option>, + order_simulator2: Option>, ) -> Self { Metrics::initialize(); Self { @@ -264,6 +267,7 @@ impl Orderbook { app_data, active_order_competition_threshold, order_simulator, + order_simulator2, } } @@ -642,7 +646,7 @@ impl Orderbook { uid: &OrderUid, block_number: Option, ) -> Result, OrderSimulationError> { - let Some(order_simulator) = &self.order_simulator else { + let Some(order_simulator) = &self.order_simulator2 else { return Err(OrderSimulationError::NotEnabled); }; let Some(order) = self @@ -653,18 +657,35 @@ impl Orderbook { return Ok(None); }; - let (_, wrappers) = self - .parse_interactions_and_wrappers( - order.metadata.full_app_data.as_deref().unwrap_or_default(), - ) - .map_err(OrderSimulationError::Other)?; + // let app_data = self + // .parse_app_data(order.metadata.full_app_data.as_deref(). + // unwrap_or_default()) .map_err(OrderSimulationError::Other)?; + + let sim = order_simulator.new_simulation_builder().add_order( + simulator::simulation_builder::Order::new(order.data) + .with_signature(order.metadata.owner, order.signature) + // properly populate those values + .with_pre_interactions(vec![]) + .with_post_interactions(vec![]) + .with_executed_amount(simulator::simulation_builder::ExecutionAmount::Remaining), + ) + .at_block(block_number.map(simulator::simulation_builder::Block::Number).unwrap_or(simulator::simulation_builder::Block::Latest)) + .fund_settlement_contract() + .from_solver(simulator::simulation_builder::Solver::Fake(None)) + // TODO: add wrapper + .build() + .await + // TODO: proper error handling + .unwrap(); - let swap = order_simulator - .encode_order(&order, wrappers, block_number) - .await?; - Ok(Some( - order_simulator.simulate_swap(swap, block_number).await?, - )) + // TODO: error handling + let sim_res = sim.simulate_with_tenderly_report().await.unwrap(); + + Ok(Some(OrderSimulationResult { + tenderly_url: sim_res.tenderly_url, + tenderly_request: sim_res.tenderly_request, + error: sim_res.error, + })) } /// Simulates an arbitrary order without requiring it to exist in the @@ -840,6 +861,7 @@ mod tests { app_data, active_order_competition_threshold: Default::default(), order_simulator: None, + order_simulator2: None, }; // Different owner diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 912680642c..6b5b8b6971 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -411,7 +411,7 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator = if let Some(config) = config.order_simulation { + let order_simulator = if let Some(config) = &config.order_simulation { let tenderly: Option> = config.tenderly.as_ref().map(|tenderly_config| { Box::new(simulator::tenderly::TenderlyApi::new( @@ -441,6 +441,30 @@ pub async fn run(config: Configuration) { None }; + let order_simulator2 = if let Some(config) = config.order_simulation { + let tenderly: Option> = + config.tenderly.as_ref().map(|tenderly_config| { + Arc::new(simulator::tenderly::TenderlyApi::new( + tenderly_config, + &http_factory, + chain.id().to_string(), + )) as _ + }); + Some(Arc::new( + simulator::simulation_builder::SettlementSimulator::new( + settlement_contract.clone(), + Default::default(), + balance_overrider.clone(), + current_block_stream.clone(), + tenderly, + ) + .await + .unwrap(), + )) + } else { + None + }; + let orderbook = Arc::new(Orderbook::new( domain_separator, *settlement_contract.address(), @@ -450,6 +474,7 @@ pub async fn run(config: Configuration) { app_data.clone(), config.active_order_competition_threshold, order_simulator, + order_simulator2, )); check_database_connection(orderbook.as_ref()).await; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 5cdfa209aa..929753825c 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,13 +1,16 @@ use { - crate::encoding::{EncodedSettlement, Interaction, WrapperCall}, - alloy_network::Ethereum, - alloy_primitives::{Address, Bytes, U256}, - alloy_provider::{DynProvider, EthCall, Provider}, + crate::{ + encoding::{EncodedSettlement, Interaction, WrapperCall}, + tenderly::dto::StateObject, + }, + alloy_primitives::{Address, Bytes, TxKind, U256}, + alloy_provider::{DynProvider, Provider}, alloy_rpc_types::{ TransactionRequest, state::{AccountOverride, StateOverride}, }, - anyhow::Result, + alloy_transport::RpcError, + anyhow::{Context, Result}, balance_overrides::BalanceOverriding, ethrpc::block_stream::CurrentBlockWatcher, model::{ @@ -33,6 +36,7 @@ pub(crate) struct Inner { pub(crate) domain_separator: DomainSeparator, pub(crate) chain_id: u64, pub(crate) current_block: CurrentBlockWatcher, + pub(crate) tenderly: Option>, } impl SettlementSimulator { @@ -41,6 +45,7 @@ impl SettlementSimulator { flash_loan_router: Address, balance_overrides: Arc, current_block: CurrentBlockWatcher, + tenderly: Option>, ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); @@ -55,6 +60,7 @@ impl SettlementSimulator { domain_separator, chain_id, current_block, + tenderly, }))) } @@ -300,11 +306,21 @@ pub struct EthCallInputs { pub block: u64, } +/// The result of Order simulation, contains the error (if any) +/// and full Tenderly API request that can be used to resimulate +/// and debug using Tenderly +#[derive(Clone, Debug)] +pub struct TenderlyReport { + /// Full request object that can be used directly with the Tenderly API + pub tenderly_request: crate::tenderly::dto::Request, + /// Shared Tenderly simulation URL for debugging in the dashboard + pub tenderly_url: Option, + /// Any error that might have been reported during order simulation + pub error: Option, +} + impl EthCallInputs { - /// Prepares an `eth_call` with the transaction request, state overrides, - /// and block already applied. The call is not sent — callers can chain - /// additional builder methods before awaiting. - pub fn simulate(self) -> EthCall { + pub async fn simulate(self) -> Result> { self.simulator .0 .provider @@ -312,7 +328,85 @@ impl EthCallInputs { .call(self.request) .overrides(self.state_overrides) .block(self.block.into()) + .await + } + + pub async fn simulate_with_tenderly_report(self) -> Result { + // TODO: error handling + let tenderly_request = self.to_tenderly_request().unwrap(); + let tenderly_url = match &self.simulator.0.tenderly { + Some(api) => Some( + api.simulate_and_share(tenderly_request.clone()) + .await + .context("tenderly failed")?, + ), + None => None, + }; + let simulation_result = self.simulate().await; + + Ok(TenderlyReport { + tenderly_request, + tenderly_url, + error: match simulation_result { + Ok(_) => None, + Err(err) => Some(err.to_string()), + }, + }) } + + /// Converts the simulation into a request that can be simulated with + /// tenderly. + pub fn to_tenderly_request(&self) -> Result { + Ok(crate::tenderly::dto::Request { + block_number: Some(self.block), + // By default, tenderly simulates on the top of the specified block, whereas regular + // nodes simulate at the end of the specified block. This is to make + // simulation results match in case critical state changed within the block. + transaction_index: Some(-1), + network_id: self.simulator.0.chain_id.to_string(), + from: self.request.from.unwrap_or_default(), + // TODO: error handling + to: match &self.request.to.ok_or(ConversionError::MissingTo)? { + TxKind::Create => Default::default(), + TxKind::Call(to) => *to, + }, + input: self + .request + .input + .input + .as_ref() + .map(|bytes| bytes.to_vec()) + .unwrap_or_default(), + gas: self.request.gas, + gas_price: None, // use tenderly default for now + value: self.request.value, + simulation_type: Some(crate::tenderly::dto::SimulationType::Full), + state_objects: Some( + self.state_overrides + .iter() + .map(|(key, value)| { + Ok(( + *key, + StateObject::try_from(value.clone()) + .map_err(|_| ConversionError::StateOverrides)?, + )) + }) + .collect::>()?, + ), + access_list: self.request.access_list.as_ref().map(Into::into), + save: Some(true), + save_if_fails: Some(true), + ..Default::default() + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ConversionError { + #[error("simulation does not have a target")] + MissingTo, + #[error("could not convert state overrides")] + StateOverrides, } #[derive(Debug, thiserror::Error)] diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 1d0dc19082..4d0de3f49d 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -66,15 +66,17 @@ pub(crate) async fn encode( .ok_or(BuildError::MissingBuyToken)?; // Compute before clearing_prices is moved into EncodedSettlement below. - let fund_amount = builder - .fund_settlement_contract - .then(|| match order.data.kind { + let fund_amount = builder.fund_settlement_contract.then(|| { + let base_amount = match order.data.kind { OrderKind::Sell => clearing_prices[sell_token_index] .saturating_mul(executed_amount) .checked_div(clearing_prices[buy_token_index]) .unwrap_or(U256::MAX), OrderKind::Buy => executed_amount, - }); + }; + // give 1 wei extra to avoid issues with rounding divisions + base_amount.saturating_add(U256::ONE) + }); let trade = encode_trade( &order.data, diff --git a/crates/simulator/src/tenderly/mod.rs b/crates/simulator/src/tenderly/mod.rs index 3abf8c3a7e..fe6c55d66d 100644 --- a/crates/simulator/src/tenderly/mod.rs +++ b/crates/simulator/src/tenderly/mod.rs @@ -1,5 +1,5 @@ use { - crate::{ethereum::Ethereum, simulation_builder::EthCallInputs}, + crate::ethereum::Ethereum, alloy_primitives::TxKind, alloy_rpc_types::{TransactionRequest, state::StateOverride}, anyhow::{Result, anyhow}, @@ -245,15 +245,6 @@ pub fn prepare_request( }) } -pub fn request_from_eth_call(inputs: &EthCallInputs) -> Result { - prepare_request( - inputs.simulator.0.chain_id.to_string(), - &inputs.request, - inputs.state_overrides.clone(), - Some(BlockNo(inputs.block)), - ) -} - pub fn log_simulation_request( simulation_endpoint: &Url, dashboard: &Url, From 8a0cd7826ba3419d9ea6bd3bdb78908c2c358523 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 28 Apr 2026 09:29:10 +0000 Subject: [PATCH 054/154] fmt --- crates/orderbook/src/orderbook.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index ba594d3202..0cafad84d9 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -662,14 +662,17 @@ impl Orderbook { // unwrap_or_default()) .map_err(OrderSimulationError::Other)?; let sim = order_simulator.new_simulation_builder().add_order( - simulator::simulation_builder::Order::new(order.data) - .with_signature(order.metadata.owner, order.signature) - // properly populate those values - .with_pre_interactions(vec![]) - .with_post_interactions(vec![]) - .with_executed_amount(simulator::simulation_builder::ExecutionAmount::Remaining), - ) - .at_block(block_number.map(simulator::simulation_builder::Block::Number).unwrap_or(simulator::simulation_builder::Block::Latest)) + simulator::simulation_builder::Order::new(order.data) + .with_signature(order.metadata.owner, order.signature) + // properly populate those values + .with_pre_interactions(vec![]) + .with_post_interactions(vec![]) + .with_executed_amount(simulator::simulation_builder::ExecutionAmount::Remaining), + ) + .at_block( + block_number.map(simulator::simulation_builder::Block::Number) + .unwrap_or(simulator::simulation_builder::Block::Latest) + ) .fund_settlement_contract() .from_solver(simulator::simulation_builder::Solver::Fake(None)) // TODO: add wrapper From dcdd98654984396320acaa33b2379ee1c11bc4c7 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 28 Apr 2026 11:50:22 +0000 Subject: [PATCH 055/154] more fixes --- crates/orderbook/src/orderbook.rs | 80 +++++++++++++++------ crates/simulator/src/encoding.rs | 9 ++- crates/simulator/src/simulation_builder.rs | 32 +++++---- crates/simulator/src/simulation_encoding.rs | 4 +- 4 files changed, 82 insertions(+), 43 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 0cafad84d9..a5e1fdb0fa 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -42,7 +42,7 @@ use { is_order_outside_market_price, }, }, - simulator::simulation_builder::SettlementSimulator, + simulator::simulation_builder::{self, SettlementSimulator}, std::{borrow::Cow, sync::Arc}, strum::Display, thiserror::Error, @@ -657,37 +657,71 @@ impl Orderbook { return Ok(None); }; - // let app_data = self - // .parse_app_data(order.metadata.full_app_data.as_deref(). - // unwrap_or_default()) .map_err(OrderSimulationError::Other)?; + let full_app_data = order + .metadata + .full_app_data + .as_ref() + .ok_or_else(|| OrderSimulationError::Other(anyhow!("can't find appdata")))?; - let sim = order_simulator.new_simulation_builder().add_order( - simulator::simulation_builder::Order::new(order.data) + let parsed_app_data = self + .order_validator + .validate_app_data( + &OrderCreationAppData::Full { + full: full_app_data.clone(), + }, + &None, + ) + .map_err(|err| OrderSimulationError::Other(anyhow::anyhow!("{:?}", err)))? + .inner + .protocol; + + let wrapper = match (parsed_app_data.flashloan, parsed_app_data.wrappers) { + (None, wrappers) if wrappers.is_empty() => simulation_builder::WrapperConfig::NoWrapper, + (None, wrappers) if !wrappers.is_empty() => { + // TODO: convert wrappers + simulation_builder::WrapperConfig::Custom(vec![]) + } + (Some(flashloan), wrappers) if wrappers.is_empty() => { + // TODO: convert flashloan + simulation_builder::WrapperConfig::Flashloan(vec![]) + } + _ => { + return Err(OrderSimulationError::Other(anyhow!( + "can't configure custom wrappers and flashloans at the same time" + ))); + } + }; + + let sim = order_simulator + .new_simulation_builder() + .add_order( + simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature) - // properly populate those values - .with_pre_interactions(vec![]) - .with_post_interactions(vec![]) - .with_executed_amount(simulator::simulation_builder::ExecutionAmount::Remaining), + .with_pre_interactions(order.interactions.pre) + .with_post_interactions(order.interactions.post) + .with_executed_amount(simulation_builder::ExecutionAmount::Remaining), ) .at_block( - block_number.map(simulator::simulation_builder::Block::Number) - .unwrap_or(simulator::simulation_builder::Block::Latest) + block_number + .map(simulation_builder::Block::Number) + .unwrap_or(simulation_builder::Block::Latest), ) - .fund_settlement_contract() - .from_solver(simulator::simulation_builder::Solver::Fake(None)) - // TODO: add wrapper + .fund_settlement_contract_with_buy_tokens() + .from_solver(simulation_builder::Solver::Fake(None)) + .with_wrapper(wrapper) .build() .await - // TODO: proper error handling - .unwrap(); + .context("failed to finalize simulation")?; - // TODO: error handling - let sim_res = sim.simulate_with_tenderly_report().await.unwrap(); + let simulation_result = sim + .simulate_with_tenderly_report() + .await + .context("failed to execute simulation")?; Ok(Some(OrderSimulationResult { - tenderly_url: sim_res.tenderly_url, - tenderly_request: sim_res.tenderly_request, - error: sim_res.error, + tenderly_url: simulation_result.tenderly_url, + tenderly_request: simulation_result.tenderly_request, + error: simulation_result.error, })) } @@ -764,7 +798,7 @@ pub enum OrderSimulationError { #[error("order simulation is not enabled")] NotEnabled, #[error("malformed input")] - MalformedInput(anyhow::Error), + MalformedInput(#[from] anyhow::Error), #[error("simulation could not be created for order")] Other(anyhow::Error), } diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index 8c6d188c24..61b3db0105 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -241,9 +241,12 @@ impl From for Interaction { } } -pub fn encode_interactions<'a>( - interactions: impl IntoIterator, -) -> Vec { +pub fn encode_interactions<'a, I>( + interactions: impl IntoIterator, +) -> Vec +where + I: InteractionEncoding + 'a, +{ interactions.into_iter().map(|i| i.encode()).collect() } diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 929753825c..8799783121 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,6 +1,6 @@ use { crate::{ - encoding::{EncodedSettlement, Interaction, WrapperCall}, + encoding::{EncodedSettlement, WrapperCall}, tenderly::dto::StateObject, }, alloy_primitives::{Address, Bytes, TxKind, U256}, @@ -15,6 +15,7 @@ use { ethrpc::block_stream::CurrentBlockWatcher, model::{ DomainSeparator, + interaction::InteractionData, order::OrderData, signature::{Signature, SigningScheme}, }, @@ -71,7 +72,7 @@ impl SettlementSimulator { pre_interactions: vec![], main_interactions: vec![], post_interactions: vec![], - wrapper: None, + wrapper: WrapperConfig::NoWrapper, prices: None, solver: None, auction_id: None, @@ -95,10 +96,10 @@ pub enum Block { /// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. pub struct SimulationBuilder { pub(crate) order: Option, - pub(crate) pre_interactions: Vec, - pub(crate) main_interactions: Vec, - pub(crate) post_interactions: Vec, - pub(crate) wrapper: Option, + pub(crate) pre_interactions: Vec, + pub(crate) main_interactions: Vec, + pub(crate) post_interactions: Vec, + pub(crate) wrapper: WrapperConfig, pub(crate) prices: Option, pub(crate) solver: Option, pub(crate) auction_id: Option, @@ -116,23 +117,23 @@ impl SimulationBuilder { self } - pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { + pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { self.pre_interactions = interactions; self } - pub fn with_main_interactions(mut self, interactions: Vec) -> Self { + pub fn with_main_interactions(mut self, interactions: Vec) -> Self { self.main_interactions = interactions; self } - pub fn with_post_interactions(mut self, interactions: Vec) -> Self { + pub fn with_post_interactions(mut self, interactions: Vec) -> Self { self.post_interactions = interactions; self } pub fn with_wrapper(mut self, wrapper: WrapperConfig) -> Self { - self.wrapper = Some(wrapper); + self.wrapper = wrapper; self } @@ -169,7 +170,7 @@ impl SimulationBuilder { /// Override the settlement contract's buy token balance so it can pay out /// the order without any external liquidity. The required amount is derived /// from the order's executed amount and clearing prices at `build()` time. - pub fn fund_settlement_contract(mut self) -> Self { + pub fn fund_settlement_contract_with_buy_tokens(mut self) -> Self { self.fund_settlement_contract = true; self } @@ -244,8 +245,8 @@ pub struct Order { pub(crate) data: OrderData, pub(crate) owner: Address, pub(crate) signature: Signature, - pub(crate) pre_interactions: Vec, - pub(crate) post_interactions: Vec, + pub(crate) pre_interactions: Vec, + pub(crate) post_interactions: Vec, pub(crate) executed_amount: ExecutionAmount, } @@ -254,6 +255,7 @@ pub struct Order { pub enum WrapperConfig { Flashloan(Vec), Custom(Vec), + NoWrapper, } pub struct FlashloanRequest { @@ -281,12 +283,12 @@ impl Order { self } - pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { + pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { self.pre_interactions = interactions; self } - pub fn with_post_interactions(mut self, interactions: Vec) -> Self { + pub fn with_post_interactions(mut self, interactions: Vec) -> Self { self.post_interactions = interactions; self } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 4d0de3f49d..e2723d25c2 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -114,10 +114,10 @@ pub(crate) async fn encode( }; let (to, input) = match builder.wrapper { - Some(WrapperConfig::Custom(wrappers)) if !wrappers.is_empty() => { + WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") } - Some(WrapperConfig::Flashloan(loans)) => { + WrapperConfig::Flashloan(loans) => { let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { loans: loans .into_iter() From fbe765ce58a579876882d9391cc88cf45680b369 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 28 Apr 2026 13:04:38 +0100 Subject: [PATCH 056/154] Comment --- crates/shared/src/order_validation.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 8bc6a86f3b..5690561899 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -63,7 +63,10 @@ pub enum Eip1271SimulationError { Infra(anyhow::Error), } -/// Simulates an EIP-1271 order end-to-end (pre-hooks → swap → post-hooks). +/// Simulates an EIP-1271 order's pre-hooks, swap, and post-hooks against +/// the chain. Does not re-prove `isValidSignature`: the signer contract +/// is replaced by `Trader.sol` during simulation, so signature correctness +/// is still gated by the cheap `signature_validator` check. /// /// Defined here rather than in `crates/simulator` because `OrderValidator` /// cannot depend on `orderbook`, where the concrete implementation lives. From 6ecd60048eac26278cebd164be754b981393a35f Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 07:07:36 +0000 Subject: [PATCH 057/154] correct tenderly block --- crates/simulator/src/simulation_builder.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 8799783121..e10ed6f5c6 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -360,11 +360,10 @@ impl EthCallInputs { /// tenderly. pub fn to_tenderly_request(&self) -> Result { Ok(crate::tenderly::dto::Request { - block_number: Some(self.block), - // By default, tenderly simulates on the top of the specified block, whereas regular - // nodes simulate at the end of the specified block. This is to make - // simulation results match in case critical state changed within the block. - transaction_index: Some(-1), + // By default tenderly simulates calls at the start of the block. So if we simulate + // something when the latest block is `n` we need to tell tenderly to simulate at + // block `n+1` to still have all of block n's txs happen before our simulation runs. + block_number: Some(self.block + 1), network_id: self.simulator.0.chain_id.to_string(), from: self.request.from.unwrap_or_default(), // TODO: error handling From 93a8dda7d07b236da96f0503326e52936aff7cfe Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 08:50:05 +0000 Subject: [PATCH 058/154] comment for idea --- crates/orderbook/src/orderbook.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index a5e1fdb0fa..520bcb0e0c 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -663,6 +663,7 @@ impl Orderbook { .as_ref() .ok_or_else(|| OrderSimulationError::Other(anyhow!("can't find appdata")))?; + // TODO: move this logic into builder by introducing `WrapperConfig::FromAppData(String)`? let parsed_app_data = self .order_validator .validate_app_data( From 5c1f7c3e262148f692a5e6933f00c1f03baa7eb8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 11:52:13 +0000 Subject: [PATCH 059/154] extract wrappers from appdata --- crates/orderbook/src/orderbook.rs | 36 ++--------------- crates/simulator/src/simulation_builder.rs | 7 ++++ crates/simulator/src/simulation_encoding.rs | 44 ++++++++++++++++++++- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 520bcb0e0c..69b4f8f754 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -660,39 +660,9 @@ impl Orderbook { let full_app_data = order .metadata .full_app_data - .as_ref() + .clone() .ok_or_else(|| OrderSimulationError::Other(anyhow!("can't find appdata")))?; - // TODO: move this logic into builder by introducing `WrapperConfig::FromAppData(String)`? - let parsed_app_data = self - .order_validator - .validate_app_data( - &OrderCreationAppData::Full { - full: full_app_data.clone(), - }, - &None, - ) - .map_err(|err| OrderSimulationError::Other(anyhow::anyhow!("{:?}", err)))? - .inner - .protocol; - - let wrapper = match (parsed_app_data.flashloan, parsed_app_data.wrappers) { - (None, wrappers) if wrappers.is_empty() => simulation_builder::WrapperConfig::NoWrapper, - (None, wrappers) if !wrappers.is_empty() => { - // TODO: convert wrappers - simulation_builder::WrapperConfig::Custom(vec![]) - } - (Some(flashloan), wrappers) if wrappers.is_empty() => { - // TODO: convert flashloan - simulation_builder::WrapperConfig::Flashloan(vec![]) - } - _ => { - return Err(OrderSimulationError::Other(anyhow!( - "can't configure custom wrappers and flashloans at the same time" - ))); - } - }; - let sim = order_simulator .new_simulation_builder() .add_order( @@ -709,7 +679,9 @@ impl Orderbook { ) .fund_settlement_contract_with_buy_tokens() .from_solver(simulation_builder::Solver::Fake(None)) - .with_wrapper(wrapper) + .with_wrapper(simulation_builder::WrapperConfig::FromAppData( + full_app_data, + )) .build() .await .context("failed to finalize simulation")?; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index e10ed6f5c6..d5ac64b19a 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -256,6 +256,9 @@ pub enum WrapperConfig { Flashloan(Vec), Custom(Vec), NoWrapper, + /// Parse the app data JSON string and extract flashloan/wrapper + /// configuration. + FromAppData(String), } pub struct FlashloanRequest { @@ -426,4 +429,8 @@ pub enum BuildError { NoPriceEncoding, #[error("failed to query filled amount from settlement contract: {0}")] FilledAmountQuery(#[source] anyhow::Error), + #[error("failed to parse app data: {0}")] + AppDataParse(#[source] serde_json::Error), + #[error("both wrappers and flashloans cannot be encoded in the same settlement")] + FlashloanWrappersIncompatible, } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index e2723d25c2..71e57aa3e9 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -3,6 +3,7 @@ use { encoding::{ EncodedSettlement, Interactions, + WrapperCall, encode_interactions, encode_trade, encode_wrapper_settlement, @@ -12,6 +13,7 @@ use { BuildError, EthCallInputs, ExecutionAmount, + FlashloanRequest, Order, Prices, SimulationBuilder, @@ -113,7 +115,8 @@ pub(crate) async fn encode( bytes.into() }; - let (to, input) = match builder.wrapper { + let wrapper = resolve_wrapper_config(builder.wrapper)?; + let (to, input) = match wrapper { WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") } @@ -179,6 +182,45 @@ pub(crate) async fn encode( }) } +fn resolve_wrapper_config(wrapper: WrapperConfig) -> Result { + let WrapperConfig::FromAppData(app_data_str) = wrapper else { + return Ok(wrapper); + }; + + let protocol = app_data::parse(app_data_str.as_bytes()).map_err(BuildError::AppDataParse)?; + + let has_wrappers = !protocol.wrappers.is_empty(); + let has_flashloan = protocol.flashloan.is_some(); + + if has_wrappers && has_flashloan { + return Err(BuildError::FlashloanWrappersIncompatible); + } + + if has_wrappers { + return Ok(WrapperConfig::Custom( + protocol + .wrappers + .into_iter() + .map(|w| WrapperCall { + address: w.address, + data: w.data.into(), + }) + .collect(), + )); + } + + if let Some(flashloan) = protocol.flashloan { + return Ok(WrapperConfig::Flashloan(vec![FlashloanRequest { + amount: flashloan.amount, + borrower: flashloan.protocol_adapter, + lender: flashloan.liquidity_provider, + token: flashloan.token, + }])); + } + + Ok(WrapperConfig::NoWrapper) +} + async fn executed_amount( builder: &SimulationBuilder, order: &Order, From 3f33170ef33d8c2ead010acbf0eb0b17d6599022 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 12:57:08 +0000 Subject: [PATCH 060/154] Populate properties from appdata --- crates/orderbook/openapi.yml | 25 +++- crates/orderbook/src/dto/mod.rs | 23 +++- crates/orderbook/src/orderbook.rs | 138 ++++++++------------ crates/orderbook/src/run.rs | 39 +----- crates/simulator/src/simulation_builder.rs | 64 ++++++++- crates/simulator/src/simulation_encoding.rs | 43 +----- 6 files changed, 159 insertions(+), 173 deletions(-) diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index b2191161ea..aa4d136b22 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -2731,24 +2731,37 @@ components: if omitted. allOf: - $ref: "#/components/schemas/Address" - nullable: true sellTokenBalance: description: Where the sell token should be drawn from. allOf: - $ref: "#/components/schemas/SellTokenSource" - default: erc20 buyTokenBalance: description: Where the buy token should be transferred to. allOf: - $ref: "#/components/schemas/BuyTokenDestination" - default: erc20 appData: description: > - Full app data JSON string. Defaults to `"{}"` if omitted. + Full app data JSON string. type: string - nullable: true blockNumber: type: integer + nullable: true + signingScheme: + $ref: "#/components/schemas/SigningScheme" + signature: + $ref: "#/components/schemas/Signature" + feeAmount: + description: > + The fee amount in sell token atoms. Expected to be 0; only present + because it must be part of the signed order data. + allOf: + - $ref: "#/components/schemas/TokenAmount" + validTo: + description: Unix timestamp (`uint32`) until which the order is valid. + type: integer + partiallyFillable: + description: Whether the order can be partially filled or must be filled all at once. + type: boolean required: - sellToken - buyToken @@ -2756,6 +2769,8 @@ components: - buyAmount - kind - owner + - signingScheme + - signature OrderSimulation: description: > The Tenderly simulation request for an order, along with any diff --git a/crates/orderbook/src/dto/mod.rs b/crates/orderbook/src/dto/mod.rs index 75b868d17f..cb5cf0b26a 100644 --- a/crates/orderbook/src/dto/mod.rs +++ b/crates/orderbook/src/dto/mod.rs @@ -4,7 +4,10 @@ pub mod order; use { alloy::primitives::U256, eth_domain_types::{Address, NonZeroU256}, - model::order::{BuyTokenDestination, OrderKind, SellTokenSource}, + model::{ + order::{BuyTokenDestination, OrderKind, SellTokenSource}, + signature::Signature, + }, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, serde_with::serde_as, @@ -45,12 +48,22 @@ pub struct OrderSimulationRequest { /// token transfer or internal Balancer Vault transfer. #[serde(default)] pub buy_token_balance: BuyTokenDestination, - /// Full app data JSON. Defaults to `"{}"` if omitted. - #[serde(default)] - pub app_data: Option, + /// Full app data JSON. + pub app_data: String, /// The block number at which the simulation should happen - #[serde(default)] pub block_number: Option, + /// The order signature (signing scheme + bytes). Required to pass + /// signature verification in the settlement contract during simulation. + #[serde(flatten)] + pub signature: Signature, + /// The fee amount signed by the user. This field is expected to be 0 and + /// only still there because it needs to be signed by the user. + pub fee_amount: U256, + /// UNIX timestamp when the order will expire. + pub valid_to: u32, + /// Whether the order needs to be filled all at once or allows to be filled + /// over multiple partial executions. + pub partially_fillable: bool, } /// The result of Order simulation, contains the error (if any) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 69b4f8f754..d7fcad8bb8 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -5,25 +5,22 @@ use { trades::{TradeFilter, TradeRetrieving}, }, dto::{self, OrderSimulationRequest, OrderSimulationResult}, - order_simulator::{self, OrderSimulator}, solver_competition::{Identifier, LoadSolverCompetitionError, SolverCompetitionStoring}, }, - alloy::primitives::{Address, B256}, + alloy::primitives::{Address, B256, keccak256}, anyhow::{Context, Result, anyhow}, - app_data::{AppDataHash, Validator, WrapperCall}, + app_data::{AppDataHash, Validator}, bigdecimal::ToPrimitive, chrono::Utc, database::order_events::OrderEventLabel, model::{ DomainSeparator, order::{ - Interactions, Order, OrderCancellation, OrderCreation, OrderCreationAppData, OrderData, - OrderMetadata, OrderStatus, OrderUid, SignedOrderCancellations, @@ -240,8 +237,7 @@ pub struct Orderbook { order_validator: Arc, app_data: Arc, active_order_competition_threshold: u32, - order_simulator: Option>, - order_simulator2: Option>, + order_simulator: Option, } impl Orderbook { @@ -254,8 +250,7 @@ impl Orderbook { order_validator: Arc, app_data: Arc, active_order_competition_threshold: u32, - order_simulator: Option>, - order_simulator2: Option>, + order_simulator: Option, ) -> Self { Metrics::initialize(); Self { @@ -267,7 +262,6 @@ impl Orderbook { app_data, active_order_competition_threshold, order_simulator, - order_simulator2, } } @@ -617,26 +611,6 @@ impl Orderbook { Ok(status) } - fn parse_interactions_and_wrappers( - &self, - full_app_data: &str, - ) -> Result<(Interactions, Vec)> { - if !full_app_data.is_empty() { - let app_data = self - .order_validator - .validate_app_data( - &OrderCreationAppData::Full { - full: full_app_data.to_string(), - }, - &None, - ) - .map_err(|err| anyhow::anyhow!("{:?}", err))?; - Ok((app_data.interactions, app_data.inner.protocol.wrappers)) - } else { - Ok((Interactions::default(), Vec::default())) - } - } - /// Simulates an order based on its Uid using the OrderSimulator. /// /// The returned value contains the simulation result and tenderly API @@ -646,7 +620,7 @@ impl Orderbook { uid: &OrderUid, block_number: Option, ) -> Result, OrderSimulationError> { - let Some(order_simulator) = &self.order_simulator2 else { + let Some(order_simulator) = &self.order_simulator else { return Err(OrderSimulationError::NotEnabled); }; let Some(order) = self @@ -668,20 +642,18 @@ impl Orderbook { .add_order( simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature) - .with_pre_interactions(order.interactions.pre) - .with_post_interactions(order.interactions.post) .with_executed_amount(simulation_builder::ExecutionAmount::Remaining), ) + .parameters_from_app_data(&full_app_data) + .context("failed to parse app data")? .at_block( block_number .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) + .with_prices(simulation_builder::Prices::Limit) .fund_settlement_contract_with_buy_tokens() .from_solver(simulation_builder::Solver::Fake(None)) - .with_wrapper(simulation_builder::WrapperConfig::FromAppData( - full_app_data, - )) .build() .await .context("failed to finalize simulation")?; @@ -707,45 +679,55 @@ impl Orderbook { let Some(order_simulator) = &self.order_simulator else { return Err(OrderSimulationError::NotEnabled); }; - let full_app_data = request.app_data.unwrap_or_default(); - let (interactions, wrappers) = self - .parse_interactions_and_wrappers(&full_app_data) - .map_err(|err| { - OrderSimulationError::MalformedInput(anyhow!("app_data `{}`: {err}", full_app_data)) - })?; - let order = Order { - metadata: OrderMetadata { - owner: request.owner, - full_app_data: Some(full_app_data), - ..Default::default() - }, - data: OrderData { - sell_token: request.sell_token, - buy_token: request.buy_token, - sell_amount: request.sell_amount.into(), - buy_amount: request.buy_amount, - kind: request.kind, - receiver: request.receiver, - sell_token_balance: request.sell_token_balance, - buy_token_balance: request.buy_token_balance, - ..Default::default() - }, - interactions, - ..Default::default() - }; + let app_data_hash = keccak256(&request.app_data); - let swap = order_simulator - .encode_order(&order, wrappers, request.block_number) + let sim = order_simulator + .new_simulation_builder() + .add_order( + simulation_builder::Order::new(OrderData { + sell_token: request.sell_token, + buy_token: request.buy_token, + sell_amount: request.sell_amount.into(), + buy_amount: request.buy_amount, + kind: request.kind, + receiver: request.receiver, + sell_token_balance: request.sell_token_balance, + buy_token_balance: request.buy_token_balance, + fee_amount: request.fee_amount, + valid_to: request.valid_to, + app_data: AppDataHash(app_data_hash.into()), + partially_fillable: request.partially_fillable, + }) + .with_signature(request.owner, request.signature) + .with_executed_amount(simulation_builder::ExecutionAmount::Full), + ) + .parameters_from_app_data(&request.app_data) + .context("failed to parse app data")? + .at_block( + request + .block_number + .map(simulation_builder::Block::Number) + .unwrap_or(simulation_builder::Block::Latest), + ) + .with_prices(simulation_builder::Prices::Limit) + .fund_settlement_contract_with_buy_tokens() + .from_solver(simulation_builder::Solver::Fake(None)) + .build() + .await + .context("failed to finalize simulation") + .map_err(OrderSimulationError::Other)?; + + let simulation_result = sim + .simulate_with_tenderly_report() .await - .map_err(|err| match err { - order_simulator::Error::Other(err) => OrderSimulationError::Other(err), - order_simulator::Error::MalformedInput(err) => { - OrderSimulationError::MalformedInput(err) - } - })?; - Ok(order_simulator - .simulate_swap(swap, request.block_number) - .await?) + .context("failed to execute simulation") + .map_err(OrderSimulationError::Other)?; + + Ok(OrderSimulationResult { + tenderly_url: simulation_result.tenderly_url, + tenderly_request: simulation_result.tenderly_request, + error: simulation_result.error, + }) } } @@ -776,15 +758,6 @@ pub enum OrderSimulationError { Other(anyhow::Error), } -impl From for OrderSimulationError { - fn from(value: order_simulator::Error) -> Self { - match value { - order_simulator::Error::Other(err) => Self::Other(err), - order_simulator::Error::MalformedInput(err) => Self::MalformedInput(err), - } - } -} - #[async_trait::async_trait] impl LivenessChecking for Orderbook { async fn is_alive(&self) -> bool { @@ -871,7 +844,6 @@ mod tests { app_data, active_order_competition_threshold: Default::default(), order_simulator: None, - order_simulator2: None, }; // Different owner diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 6b5b8b6971..ae9cdee469 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -5,7 +5,6 @@ use { database::Postgres, ipfs::Ipfs, ipfs_app_data::IpfsAppData, - order_simulator::OrderSimulator, orderbook::Orderbook, quoter::QuoteHandler, }, @@ -43,7 +42,6 @@ use { order_quoting::{self, OrderQuoter}, order_validation::{OrderValidPeriodConfiguration, OrderValidator}, }, - simulator::swap_simulator::SwapSimulator, std::{future::Future, net::SocketAddr, sync::Arc, time::Duration}, token_info::{CachedTokenInfoFetcher, TokenInfoFetcher}, tokio::task::{self, JoinHandle}, @@ -171,6 +169,7 @@ pub async fn run(config: Configuration) { .await .expect("load hooks trampoline contract"), }; + let hooks_trampoline_address = *hooks_contract.address(); verify_deployed_contract_constants(&settlement_contract, chain_id) .await @@ -411,36 +410,6 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator = if let Some(config) = &config.order_simulation { - let tenderly: Option> = - config.tenderly.as_ref().map(|tenderly_config| { - Box::new(simulator::tenderly::TenderlyApi::new( - tenderly_config, - &http_factory, - chain.id().to_string(), - )) as _ - }); - Some(Arc::new(OrderSimulator::new( - SwapSimulator::new( - balance_overrider.clone(), - *settlement_contract.address(), - *native_token.address(), - current_block_stream.clone(), - web3, - config - .gas_limit - .try_into() - .expect("gas_limit must fit in u64"), - ) - .await - .expect("failed to create SwapSimulator"), - chain.id().to_string(), - tenderly, - ))) - } else { - None - }; - let order_simulator2 = if let Some(config) = config.order_simulation { let tenderly: Option> = config.tenderly.as_ref().map(|tenderly_config| { @@ -450,17 +419,18 @@ pub async fn run(config: Configuration) { chain.id().to_string(), )) as _ }); - Some(Arc::new( + Some( simulator::simulation_builder::SettlementSimulator::new( settlement_contract.clone(), Default::default(), + hooks_trampoline_address, balance_overrider.clone(), current_block_stream.clone(), tenderly, ) .await .unwrap(), - )) + ) } else { None }; @@ -473,7 +443,6 @@ pub async fn run(config: Configuration) { order_validator.clone(), app_data.clone(), config.active_order_competition_threshold, - order_simulator, order_simulator2, )); diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index d5ac64b19a..f8202bcbe4 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -9,6 +9,7 @@ use { TransactionRequest, state::{AccountOverride, StateOverride}, }, + alloy_sol_types::SolCall, alloy_transport::RpcError, anyhow::{Context, Result}, balance_overrides::BalanceOverriding, @@ -32,6 +33,7 @@ pub(crate) struct Inner { pub(crate) settlement: contracts::GPv2Settlement::Instance, pub(crate) authenticator: Address, pub(crate) flash_loan_router: Address, + pub(crate) hooks_trampoline: Address, pub(crate) balance_overrides: Arc, pub(crate) provider: DynProvider, pub(crate) domain_separator: DomainSeparator, @@ -44,6 +46,7 @@ impl SettlementSimulator { pub async fn new( settlement: contracts::GPv2Settlement::Instance, flash_loan_router: Address, + hooks_trampoline: Address, balance_overrides: Arc, current_block: CurrentBlockWatcher, tenderly: Option>, @@ -56,6 +59,7 @@ impl SettlementSimulator { settlement, authenticator, flash_loan_router, + hooks_trampoline, balance_overrides, provider, domain_separator, @@ -167,6 +171,63 @@ impl SimulationBuilder { self } + /// Parses the app data JSON and configures the builder accordingly: + /// - Pre/post hooks are encoded as interactions via the [`HooksTrampoline`] + /// - Flashloan/wrapper fields set the [`WrapperConfig`]. + pub fn parameters_from_app_data(mut self, app_data: &str) -> Result { + let protocol = app_data::parse(app_data.as_bytes()).map_err(BuildError::AppDataParse)?; + + let encode_hooks = |hooks: &[app_data::Hook]| -> Vec { + if hooks.is_empty() { + return vec![]; + } + vec![InteractionData { + target: self.simulator.0.hooks_trampoline, + value: U256::ZERO, + call_data: contracts::HooksTrampoline::HooksTrampoline::executeCall { + hooks: hooks + .iter() + .map(|h| contracts::HooksTrampoline::HooksTrampoline::Hook { + target: h.target, + callData: Bytes::copy_from_slice(&h.call_data), + gasLimit: U256::from(h.gas_limit), + }) + .collect(), + } + .abi_encode(), + }] + }; + self.pre_interactions = encode_hooks(&protocol.hooks.pre); + self.post_interactions = encode_hooks(&protocol.hooks.post); + + let has_wrappers = !protocol.wrappers.is_empty(); + let has_flashloan = protocol.flashloan.is_some(); + if has_wrappers && has_flashloan { + return Err(BuildError::FlashloanWrappersIncompatible); + } + if has_wrappers { + self.wrapper = WrapperConfig::Custom( + protocol + .wrappers + .into_iter() + .map(|w| WrapperCall { + address: w.address, + data: w.data.into(), + }) + .collect(), + ); + } else if let Some(flashloan) = protocol.flashloan { + self.wrapper = WrapperConfig::Flashloan(vec![FlashloanRequest { + amount: flashloan.amount, + borrower: flashloan.protocol_adapter, + lender: flashloan.liquidity_provider, + token: flashloan.token, + }]); + } + + Ok(self) + } + /// Override the settlement contract's buy token balance so it can pay out /// the order without any external liquidity. The required amount is derived /// from the order's executed amount and clearing prices at `build()` time. @@ -256,9 +317,6 @@ pub enum WrapperConfig { Flashloan(Vec), Custom(Vec), NoWrapper, - /// Parse the app data JSON string and extract flashloan/wrapper - /// configuration. - FromAppData(String), } pub struct FlashloanRequest { diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 71e57aa3e9..a8edecbfab 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -3,7 +3,6 @@ use { encoding::{ EncodedSettlement, Interactions, - WrapperCall, encode_interactions, encode_trade, encode_wrapper_settlement, @@ -13,7 +12,6 @@ use { BuildError, EthCallInputs, ExecutionAmount, - FlashloanRequest, Order, Prices, SimulationBuilder, @@ -115,7 +113,7 @@ pub(crate) async fn encode( bytes.into() }; - let wrapper = resolve_wrapper_config(builder.wrapper)?; + let wrapper = builder.wrapper; let (to, input) = match wrapper { WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => { encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") @@ -182,45 +180,6 @@ pub(crate) async fn encode( }) } -fn resolve_wrapper_config(wrapper: WrapperConfig) -> Result { - let WrapperConfig::FromAppData(app_data_str) = wrapper else { - return Ok(wrapper); - }; - - let protocol = app_data::parse(app_data_str.as_bytes()).map_err(BuildError::AppDataParse)?; - - let has_wrappers = !protocol.wrappers.is_empty(); - let has_flashloan = protocol.flashloan.is_some(); - - if has_wrappers && has_flashloan { - return Err(BuildError::FlashloanWrappersIncompatible); - } - - if has_wrappers { - return Ok(WrapperConfig::Custom( - protocol - .wrappers - .into_iter() - .map(|w| WrapperCall { - address: w.address, - data: w.data.into(), - }) - .collect(), - )); - } - - if let Some(flashloan) = protocol.flashloan { - return Ok(WrapperConfig::Flashloan(vec![FlashloanRequest { - amount: flashloan.amount, - borrower: flashloan.protocol_adapter, - lender: flashloan.liquidity_provider, - token: flashloan.token, - }])); - } - - Ok(WrapperConfig::NoWrapper) -} - async fn executed_amount( builder: &SimulationBuilder, order: &Order, From 7d0253a94da95e928df9ec38e962d5a756d9be23 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 13:00:54 +0000 Subject: [PATCH 061/154] .context() instead of unwrap() --- crates/simulator/src/simulation_builder.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f8202bcbe4..b7a20307e9 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -395,8 +395,9 @@ impl EthCallInputs { } pub async fn simulate_with_tenderly_report(self) -> Result { - // TODO: error handling - let tenderly_request = self.to_tenderly_request().unwrap(); + let tenderly_request = self + .to_tenderly_request() + .context("failed to convert to tenderly request")?; let tenderly_url = match &self.simulator.0.tenderly { Some(api) => Some( api.simulate_and_share(tenderly_request.clone()) From 764d3e732491063bf42062df2d2412fc428b4b37 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 13:45:54 +0000 Subject: [PATCH 062/154] fix tests --- crates/e2e/tests/e2e/order_simulation.rs | 58 ++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/crates/e2e/tests/e2e/order_simulation.rs b/crates/e2e/tests/e2e/order_simulation.rs index e7c63dc762..d5a61f75a3 100644 --- a/crates/e2e/tests/e2e/order_simulation.rs +++ b/crates/e2e/tests/e2e/order_simulation.rs @@ -1,10 +1,19 @@ use { - alloy::{primitives::Address, providers::Provider}, + alloy::{ + primitives::{Address, ruint::aliases::U256}, + providers::Provider, + }, configs::test_util::TestDefault, e2e::setup::{API_HOST, OnchainComponents, Services, TIMEOUT, run_test, wait_for_condition}, ethrpc::{Web3, alloy::CallBuilderExt}, model::{ - order::{OrderCreation, OrderKind}, + order::{ + BuyTokenDestination, + OrderCreation, + OrderCreationAppData, + OrderKind, + SellTokenSource, + }, signature::EcdsaSigningScheme, }, number::units::EthUnit, @@ -50,15 +59,53 @@ async fn custom_order_simulation(web3: Web3) { .await; let client = services.client(); + let valid_to = model::time::now_in_epoch_seconds() + 100; + let sell_amount = 1u64.eth(); - let request = orderbook::dto::OrderSimulationRequest { + let app_data = "{}"; + + // Sign an OrderCreation with the same fields the simulation will encode. + // simulate_custom_order hashes `app_data` with keccak256; OrderCreation::Full + // does the same, so the signature covers exactly the same OrderData hash. + let signed = OrderCreation { sell_token: *token.address(), buy_token: *onchain.contracts().weth.address(), - sell_amount: sell_amount.try_into().expect("Sell amount is non zero"), + sell_amount, buy_amount: 1u64.eth(), kind: OrderKind::Sell, - owner: trader.address(), + receiver: Some(Address::default()), + sell_token_balance: SellTokenSource::Erc20, + buy_token_balance: BuyTokenDestination::Erc20, + fee_amount: U256::ZERO, + valid_to, + app_data: OrderCreationAppData::Full { + full: app_data.to_string(), + }, + partially_fillable: false, ..Default::default() + } + .sign( + EcdsaSigningScheme::Eip712, + &onchain.contracts().domain_separator, + &trader.signer, + ); + + let request = orderbook::dto::OrderSimulationRequest { + sell_token: signed.sell_token, + buy_token: signed.buy_token, + sell_amount: signed.sell_amount.try_into().unwrap(), + buy_amount: signed.buy_amount, + kind: signed.kind, + owner: trader.address(), + receiver: signed.receiver, + sell_token_balance: signed.sell_token_balance, + buy_token_balance: signed.buy_token_balance, + app_data: app_data.to_string(), + block_number: None, + signature: signed.signature, + fee_amount: signed.fee_amount, + valid_to: signed.valid_to, + partially_fillable: signed.partially_fillable, }; // Trader has no sell tokens — simulation should revert. @@ -368,6 +415,7 @@ async fn order_simulation_partial_fill(web3: Web3) { .unwrap(); assert_eq!(response.status(), StatusCode::OK); let result = response.json::().await.unwrap(); + tracing::error!(?result); assert_eq!( result.error, None, From 934da3977a3b4cdef63f4fd8eb81bf38ae8de3bd Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 29 Apr 2026 13:52:37 +0000 Subject: [PATCH 063/154] remove test log --- crates/e2e/tests/e2e/order_simulation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/e2e/tests/e2e/order_simulation.rs b/crates/e2e/tests/e2e/order_simulation.rs index d5a61f75a3..1217ed2d69 100644 --- a/crates/e2e/tests/e2e/order_simulation.rs +++ b/crates/e2e/tests/e2e/order_simulation.rs @@ -415,7 +415,6 @@ async fn order_simulation_partial_fill(web3: Web3) { .unwrap(); assert_eq!(response.status(), StatusCode::OK); let result = response.json::().await.unwrap(); - tracing::error!(?result); assert_eq!( result.error, None, From 66eae59a6210ab996132a3e269689cd2bea3da19 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 16:13:31 +0100 Subject: [PATCH 064/154] Map flashloan.protocol_adapter to FlashloanRequest.borrower --- crates/orderbook/src/eip1271_simulation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index 08468894dc..b1c2f1b1a2 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -39,7 +39,7 @@ impl Eip1271Simulating for OrderSimulatorAdapter { let wrapper = match flashloan { Some(loan) => WrapperConfig::Flashloan(vec![FlashloanRequest { amount: loan.amount, - borrower: loan.receiver, + borrower: loan.protocol_adapter, lender: loan.liquidity_provider, token: loan.token, }]), From 7ea15517b98c6132ea8dbbf5899bbf187826f130 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 16:27:03 +0100 Subject: [PATCH 065/154] Add local-node e2e for Eip1271SimulationMode::Enforce --- .../tests/e2e/eip1271_creation_simulation.rs | 152 ++++++++++++++++++ crates/e2e/tests/e2e/main.rs | 1 + 2 files changed, 153 insertions(+) create mode 100644 crates/e2e/tests/e2e/eip1271_creation_simulation.rs diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs new file mode 100644 index 0000000000..904996effa --- /dev/null +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -0,0 +1,152 @@ +//! Local-node tests for the EIP-1271 creation-time simulation. +//! +//! With the orderbook configured in `Eip1271SimulationMode::Enforce`, an order +//! whose simulation reverts must be rejected at creation with HTTP 400 and an +//! `Eip1271SimulationFailed` body. A well-formed order must be accepted. + +use { + app_data::Hook, + configs::{orderbook::Eip1271SimulationMode, test_util::TestDefault}, + e2e::setup::{MintableToken, OnchainComponents, Services, run_test, safe::Safe}, + model::order::{OrderCreation, OrderCreationAppData, OrderKind}, + number::units::EthUnit, + reqwest::StatusCode, + serde_json::json, + shared::web3::Web3, +}; + +#[tokio::test] +#[ignore] +async fn local_node_eip1271_creation_simulation_rejects_buggy_pre_hook() { + run_test(rejects_buggy_pre_hook).await; +} + +#[tokio::test] +#[ignore] +async fn local_node_eip1271_creation_simulation_accepts_valid_order() { + run_test(accepts_valid_order).await; +} + +async fn rejects_buggy_pre_hook(web3: Web3) { + let mut onchain = OnchainComponents::deploy(web3.clone()).await; + let [solver] = onchain.make_solvers(1u64.eth()).await; + let [trader] = onchain.make_accounts(1u64.eth()).await; + + let safe = Safe::deploy(trader, web3.provider.clone()).await; + + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(100_000u64.eth(), 100_000u64.eth()) + .await; + fund_safe(&safe, &token, &onchain).await; + + let services = start_services_in_enforce_mode(&onchain, solver).await; + + // Counter has only `incrementCounter` and `setCounterToBalance`. Calling + // it with selector 0xdeadbeef hits no dispatch and reverts. + let counter = contracts::test::Counter::Instance::deploy(web3.provider.clone()) + .await + .unwrap(); + let buggy_pre_hook = Hook { + target: *counter.address(), + call_data: vec![0xde, 0xad, 0xbe, 0xef], + gas_limit: 100_000, + }; + + let order = sign_order_with_hooks(&safe, &onchain, &token, vec![buggy_pre_hook], vec![]); + + let err = services.create_order(&order).await.unwrap_err(); + assert_eq!(err.0, StatusCode::BAD_REQUEST, "body: {}", err.1); + assert!( + err.1.contains("Eip1271SimulationFailed"), + "expected Eip1271SimulationFailed in body, got: {}", + err.1 + ); +} + +async fn accepts_valid_order(web3: Web3) { + let mut onchain = OnchainComponents::deploy(web3.clone()).await; + let [solver] = onchain.make_solvers(1u64.eth()).await; + let [trader] = onchain.make_accounts(1u64.eth()).await; + + let safe = Safe::deploy(trader, web3.provider.clone()).await; + + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(100_000u64.eth(), 100_000u64.eth()) + .await; + fund_safe(&safe, &token, &onchain).await; + + let services = start_services_in_enforce_mode(&onchain, solver).await; + + let order = sign_order_with_hooks(&safe, &onchain, &token, vec![], vec![]); + + let uid = services + .create_order(&order) + .await + .expect("expected order to be accepted"); + let stored = services.get_order(&uid).await.unwrap(); + assert_eq!(stored.metadata.uid, uid); +} + +async fn fund_safe(safe: &Safe, token: &MintableToken, onchain: &OnchainComponents) { + token.mint(safe.address(), 10u64.eth()).await; + safe.exec_alloy_call( + token + .approve(onchain.contracts().allowance, 10u64.eth()) + .into_transaction_request(), + ) + .await; +} + +async fn start_services_in_enforce_mode<'a>( + onchain: &'a OnchainComponents, + solver: e2e::setup::onchain_components::TestAccount, +) -> Services<'a> { + let mut orderbook_config = configs::orderbook::Configuration::test_default(); + orderbook_config + .order_simulation + .as_mut() + .expect("test_default enables order_simulation") + .eip1271_simulation_mode = Eip1271SimulationMode::Enforce; + + let services = Services::new(onchain).await; + services + .start_protocol_with_args( + configs::autopilot::Configuration::test("test_solver", solver.address()), + orderbook_config, + solver, + ) + .await; + services +} + +fn sign_order_with_hooks( + safe: &Safe, + onchain: &OnchainComponents, + sell_token: &MintableToken, + pre: Vec, + post: Vec, +) -> OrderCreation { + let app_data = json!({ + "metadata": { + "hooks": { + "pre": pre, + "post": post, + }, + }, + }) + .to_string(); + + let mut order = OrderCreation { + kind: OrderKind::Sell, + sell_token: *sell_token.address(), + sell_amount: 5u64.eth(), + buy_token: *onchain.contracts().weth.address(), + buy_amount: 1u64.eth(), + valid_to: model::time::now_in_epoch_seconds() + 300, + from: Some(safe.address()), + app_data: OrderCreationAppData::Full { full: app_data }, + ..Default::default() + }; + safe.sign_order(&mut order, onchain); + order +} diff --git a/crates/e2e/tests/e2e/main.rs b/crates/e2e/tests/e2e/main.rs index fdf61242bf..90cc363be7 100644 --- a/crates/e2e/tests/e2e/main.rs +++ b/crates/e2e/tests/e2e/main.rs @@ -15,6 +15,7 @@ mod cow_amm; mod database; mod debug_order; mod deprecated_endpoints; +mod eip1271_creation_simulation; mod eip4626; mod eth_integration; mod eth_safe; From 8e507065af7ce622a727dd46c5b88e1ed9347dde Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 16:59:29 +0100 Subject: [PATCH 066/154] Wrap simulation via parameters_from_app_data and switch e2e to revert wrapper --- .../tests/e2e/eip1271_creation_simulation.rs | 105 +++++++++++++----- crates/orderbook/src/eip1271_simulation.rs | 20 +--- crates/shared/src/order_validation.rs | 26 ++--- 3 files changed, 91 insertions(+), 60 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 904996effa..0243a91334 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -1,11 +1,22 @@ //! Local-node tests for the EIP-1271 creation-time simulation. //! -//! With the orderbook configured in `Eip1271SimulationMode::Enforce`, an order -//! whose simulation reverts must be rejected at creation with HTTP 400 and an -//! `Eip1271SimulationFailed` body. A well-formed order must be accepted. +//! Two cases: +//! +//! - With the orderbook in `Eip1271SimulationMode::Enforce`, an order whose +//! simulated settlement reverts must be rejected at creation with HTTP 400 +//! `Eip1271SimulationFailed`. We trigger this by routing the simulation +//! through a tiny always-revert wrapper contract referenced from +//! `app_data.protocol.wrappers`. Reverts coming from a wrapper bypass the +//! `HooksTrampoline` revert-swallow, so the simulation actually fails. +//! - A well-formed Safe-signed order with no wrapper must be accepted (HTTP +//! 200), proving the adapter wiring lets healthy orders through. use { - app_data::Hook, + alloy::{ + primitives::{Address, Bytes, hex}, + providers::Provider, + rpc::types::TransactionRequest, + }, configs::{orderbook::Eip1271SimulationMode, test_util::TestDefault}, e2e::setup::{MintableToken, OnchainComponents, Services, run_test, safe::Safe}, model::order::{OrderCreation, OrderCreationAppData, OrderKind}, @@ -15,10 +26,18 @@ use { shared::web3::Web3, }; +/// Constructor + runtime that always reverts with empty data on any call. +/// +/// Constructor copies the 5-byte runtime (`PUSH1 0; PUSH1 0; REVERT`) into +/// memory and returns it. The deployed contract reverts unconditionally +/// regardless of selector or calldata. +const ALWAYS_REVERT_INIT_CODE: [u8; 17] = + hex!("6005600c60003960056000f360006000fd"); + #[tokio::test] #[ignore] -async fn local_node_eip1271_creation_simulation_rejects_buggy_pre_hook() { - run_test(rejects_buggy_pre_hook).await; +async fn local_node_eip1271_creation_simulation_rejects_when_wrapper_reverts() { + run_test(rejects_when_wrapper_reverts).await; } #[tokio::test] @@ -27,12 +46,12 @@ async fn local_node_eip1271_creation_simulation_accepts_valid_order() { run_test(accepts_valid_order).await; } -async fn rejects_buggy_pre_hook(web3: Web3) { +async fn rejects_when_wrapper_reverts(web3: Web3) { let mut onchain = OnchainComponents::deploy(web3.clone()).await; let [solver] = onchain.make_solvers(1u64.eth()).await; let [trader] = onchain.make_accounts(1u64.eth()).await; - let safe = Safe::deploy(trader, web3.provider.clone()).await; + let safe = Safe::deploy(trader.clone(), web3.provider.clone()).await; let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(100_000u64.eth(), 100_000u64.eth()) @@ -41,18 +60,17 @@ async fn rejects_buggy_pre_hook(web3: Web3) { let services = start_services_in_enforce_mode(&onchain, solver).await; - // Counter has only `incrementCounter` and `setCounterToBalance`. Calling - // it with selector 0xdeadbeef hits no dispatch and reverts. - let counter = contracts::test::Counter::Instance::deploy(web3.provider.clone()) - .await - .unwrap(); - let buggy_pre_hook = Hook { - target: *counter.address(), - call_data: vec![0xde, 0xad, 0xbe, 0xef], - gas_limit: 100_000, - }; + let wrapper_addr = deploy_always_revert(&web3, trader.address()).await; - let order = sign_order_with_hooks(&safe, &onchain, &token, vec![buggy_pre_hook], vec![]); + let order = sign_order( + &safe, + &onchain, + &token, + Some(WrapperRef { + address: wrapper_addr, + data: vec![], + }), + ); let err = services.create_order(&order).await.unwrap_err(); assert_eq!(err.0, StatusCode::BAD_REQUEST, "body: {}", err.1); @@ -77,7 +95,7 @@ async fn accepts_valid_order(web3: Web3) { let services = start_services_in_enforce_mode(&onchain, solver).await; - let order = sign_order_with_hooks(&safe, &onchain, &token, vec![], vec![]); + let order = sign_order(&safe, &onchain, &token, None); let uid = services .create_order(&order) @@ -87,6 +105,25 @@ async fn accepts_valid_order(web3: Web3) { assert_eq!(stored.metadata.uid, uid); } +/// Deploys a contract whose runtime is `PUSH1 0; PUSH1 0; REVERT`. +async fn deploy_always_revert(web3: &Web3, from: Address) -> Address { + let receipt = web3 + .provider + .send_transaction( + TransactionRequest::default() + .from(from) + .input(Bytes::from(ALWAYS_REVERT_INIT_CODE.to_vec()).into()), + ) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + receipt + .contract_address + .expect("deployment receipt should carry the contract address") +} + async fn fund_safe(safe: &Safe, token: &MintableToken, onchain: &OnchainComponents) { token.mint(safe.address(), 10u64.eth()).await; safe.exec_alloy_call( @@ -119,21 +156,29 @@ async fn start_services_in_enforce_mode<'a>( services } -fn sign_order_with_hooks( +struct WrapperRef { + address: Address, + data: Vec, +} + +fn sign_order( safe: &Safe, onchain: &OnchainComponents, sell_token: &MintableToken, - pre: Vec, - post: Vec, + wrapper: Option, ) -> OrderCreation { - let app_data = json!({ - "metadata": { - "hooks": { - "pre": pre, - "post": post, + let app_data = match wrapper { + Some(w) => json!({ + "metadata": { + "wrappers": [{ + "address": format!("{:?}", w.address), + "data": format!("0x{}", hex::encode(&w.data)), + "isOmittable": false, + }], }, - }, - }) + }), + None => json!({}), + } .to_string(); let mut order = OrderCreation { diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index b1c2f1b1a2..00d60172cd 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -1,6 +1,5 @@ use { anyhow::anyhow, - app_data::Flashloan, async_trait::async_trait, model::order::Order, shared::order_validation::{Eip1271Simulating, Eip1271SimulationError}, @@ -8,11 +7,9 @@ use { self, Block, ExecutionAmount, - FlashloanRequest, Prices, SettlementSimulator, Solver, - WrapperConfig, }, }; @@ -34,31 +31,20 @@ impl Eip1271Simulating for OrderSimulatorAdapter { async fn simulate( &self, order: &Order, - flashloan: Option, + full_app_data: String, ) -> Result<(), Eip1271SimulationError> { - let wrapper = match flashloan { - Some(loan) => WrapperConfig::Flashloan(vec![FlashloanRequest { - amount: loan.amount, - borrower: loan.protocol_adapter, - lender: loan.liquidity_provider, - token: loan.token, - }]), - None => WrapperConfig::NoWrapper, - }; - let inputs = self .inner .new_simulation_builder() .add_order( simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature.clone()) - .with_pre_interactions(order.interactions.pre.clone()) - .with_post_interactions(order.interactions.post.clone()) .with_executed_amount(ExecutionAmount::Full), ) + .parameters_from_app_data(&full_app_data) + .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("parse app data")))? .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) - .with_wrapper(wrapper) .fund_settlement_contract_with_buy_tokens() .at_block(Block::Latest) .build() diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index d1310f583f..9cca4b2f67 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -9,7 +9,7 @@ use { account_balances::{self, BalanceFetching, TransferSimulationError}, alloy::primitives::{Address, B256, U256}, anyhow::{Result, anyhow}, - app_data::{self, AppDataHash, Hook, Hooks, ValidatedAppData, Validator}, + app_data::{AppDataHash, Hook, Hooks, ValidatedAppData, Validator}, async_trait::async_trait, bad_tokens::list_based::DenyListedTokens, balance_overrides::BalanceOverrideRequest, @@ -76,7 +76,7 @@ pub trait Eip1271Simulating: Send + Sync { async fn simulate( &self, order: &Order, - flashloan: Option, + full_app_data: String, ) -> Result<(), Eip1271SimulationError>; } @@ -178,13 +178,13 @@ fn classify_signature(res: &Result) -> SignatureO async fn timed_simulation( sim: &dyn Eip1271Simulating, order: &Order, - flashloan: Option, + full_app_data: String, timeout: Duration, ) -> SimulationOutcome { let _timer = Eip1271SimulationMetrics::get() .simulation_time .start_timer(); - match tokio::time::timeout(timeout, sim.simulate(order, flashloan)).await { + match tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await { Ok(Ok(())) => SimulationOutcome::Pass, Ok(Err(Eip1271SimulationError::Reverted { reason, @@ -239,12 +239,12 @@ fn record_simulation_outcome( async fn run_eip1271_simulation_only( config: &Eip1271Simulator, preview_order: &Order, - flashloan: Option, + full_app_data: String, ) { let outcome = timed_simulation( config.simulator.as_ref(), preview_order, - flashloan, + full_app_data, config.timeout, ) .await; @@ -633,8 +633,8 @@ impl OrderValidator { uid, order.signature.clone(), ); - let flashloan = app_data.inner.protocol.flashloan.clone(); - self.run_eip1271_checks(check, &preview_order, flashloan, hash) + let full_app_data = app_data.inner.document.clone(); + self.run_eip1271_checks(check, &preview_order, full_app_data, hash) .await } @@ -652,16 +652,16 @@ impl OrderValidator { &self, check: SignatureCheck, preview_order: &Order, - flashloan: Option, + full_app_data: String, hash: B256, ) -> Result { if self.eip1271_skip_creation_validation { if let Some(config) = &self.eip1271_simulator { - run_eip1271_simulation_only(config, preview_order, flashloan).await; + run_eip1271_simulation_only(config, preview_order, full_app_data).await; } return Ok(0u64); } - self.run_eip1271_with_signature_check(check, preview_order, flashloan, hash) + self.run_eip1271_with_signature_check(check, preview_order, full_app_data, hash) .await } @@ -680,7 +680,7 @@ impl OrderValidator { &self, check: SignatureCheck, preview_order: &Order, - flashloan: Option, + full_app_data: String, hash: B256, ) -> Result { let signature_fut = self @@ -697,7 +697,7 @@ impl OrderValidator { let simulation_fut = timed_simulation( config.simulator.as_ref(), preview_order, - flashloan, + full_app_data, config.timeout, ); From d4e3050c2b5cb1e6b1053f78a95865e534da294e Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 17:10:01 +0100 Subject: [PATCH 067/154] Use TxKind::Create explicitly when deploying always-revert wrapper --- crates/e2e/tests/e2e/eip1271_creation_simulation.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 0243a91334..9804b6ae21 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -13,7 +13,7 @@ use { alloy::{ - primitives::{Address, Bytes, hex}, + primitives::{Address, Bytes, TxKind, hex}, providers::Provider, rpc::types::TransactionRequest, }, @@ -107,13 +107,13 @@ async fn accepts_valid_order(web3: Web3) { /// Deploys a contract whose runtime is `PUSH1 0; PUSH1 0; REVERT`. async fn deploy_always_revert(web3: &Web3, from: Address) -> Address { + let mut tx = TransactionRequest::default() + .from(from) + .input(Bytes::from(ALWAYS_REVERT_INIT_CODE.to_vec()).into()); + tx.to = Some(TxKind::Create); let receipt = web3 .provider - .send_transaction( - TransactionRequest::default() - .from(from) - .input(Bytes::from(ALWAYS_REVERT_INIT_CODE.to_vec()).into()), - ) + .send_transaction(tx) .await .unwrap() .get_receipt() From befab137d05971a108718ad83b0e90529b20f308 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 17:28:06 +0100 Subject: [PATCH 068/154] fmt --- crates/e2e/tests/e2e/eip1271_creation_simulation.rs | 3 +-- crates/orderbook/src/run.rs | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 9804b6ae21..5026efad97 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -31,8 +31,7 @@ use { /// Constructor copies the 5-byte runtime (`PUSH1 0; PUSH1 0; REVERT`) into /// memory and returns it. The deployed contract reverts unconditionally /// regardless of selector or calldata. -const ALWAYS_REVERT_INIT_CODE: [u8; 17] = - hex!("6005600c60003960056000f360006000fd"); +const ALWAYS_REVERT_INIT_CODE: [u8; 17] = hex!("6005600c60003960056000f360006000fd"); #[tokio::test] #[ignore] diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 19fb465fa3..c2bf0dbea7 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -410,10 +410,9 @@ pub async fn run(config: Configuration) { configs::orderbook::Eip1271SimulationMode::Disabled => None, }; let eip1271_simulator = mode.map(|mode| { - let simulator: Arc = - Arc::new(crate::eip1271_simulation::OrderSimulatorAdapter::new( - order_simulator.clone(), - )); + let simulator: Arc = Arc::new( + crate::eip1271_simulation::OrderSimulatorAdapter::new(order_simulator.clone()), + ); Eip1271Simulator { simulator, mode, From 9ae38fe2f824afbb5970a415bb13a760fdd4e618 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 17:53:53 +0100 Subject: [PATCH 069/154] nit --- crates/e2e/tests/e2e/eip1271_creation_simulation.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 5026efad97..5c9cc857eb 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -71,12 +71,11 @@ async fn rejects_when_wrapper_reverts(web3: Web3) { }), ); - let err = services.create_order(&order).await.unwrap_err(); - assert_eq!(err.0, StatusCode::BAD_REQUEST, "body: {}", err.1); + let (status, body) = services.create_order(&order).await.unwrap_err(); + assert_eq!(status, StatusCode::BAD_REQUEST, "body: {body}"); assert!( - err.1.contains("Eip1271SimulationFailed"), - "expected Eip1271SimulationFailed in body, got: {}", - err.1 + body.contains("Eip1271SimulationFailed"), + "expected Eip1271SimulationFailed in body, got: {body}", ); } From 8f72eca6e6d88ef672cb92bf81f3ff50d50defae Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 29 Apr 2026 18:21:28 +0100 Subject: [PATCH 070/154] Patch malformed-request tests to populate fields Martin made required --- crates/e2e/tests/e2e/malformed_requests.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/e2e/tests/e2e/malformed_requests.rs b/crates/e2e/tests/e2e/malformed_requests.rs index f6ccc40a66..e03c476f45 100644 --- a/crates/e2e/tests/e2e/malformed_requests.rs +++ b/crates/e2e/tests/e2e/malformed_requests.rs @@ -492,6 +492,11 @@ async fn http_validation(web3: Web3) { "kind": "sell", "owner": VALID_ADDRESS, "appData": bad_app_data, + "signingScheme": "presign", + "signature": "0x", + "feeAmount": "0", + "validTo": u32::MAX, + "partiallyFillable": false, })) .send() .await @@ -503,15 +508,10 @@ async fn http_validation(web3: Web3) { ); let body: Error = response.json().await.unwrap(); assert!( - body.description.contains("app_data"), + body.description.contains("app data"), "error description should name the failing field. Got: {}", body.description ); - assert!( - body.description.contains(bad_app_data), - "error description should include the bad value. Got: {}", - body.description - ); } #[tokio::test] @@ -563,6 +563,12 @@ async fn simulation_not_enabled(web3: Web3) { "buyAmount": "1000000000000000000", "kind": "sell", "owner": VALID_ADDRESS, + "appData": "{}", + "signingScheme": "presign", + "signature": "0x", + "feeAmount": "0", + "validTo": u32::MAX, + "partiallyFillable": false, })) .send() .await From 2fb0d7f37b5725c2ca424de04a08ee0a80d78949 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 09:50:11 +0100 Subject: [PATCH 071/154] Better comment --- .../tests/e2e/eip1271_creation_simulation.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 5c9cc857eb..aac27a6657 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -1,15 +1,15 @@ //! Local-node tests for the EIP-1271 creation-time simulation. //! -//! Two cases: -//! -//! - With the orderbook in `Eip1271SimulationMode::Enforce`, an order whose -//! simulated settlement reverts must be rejected at creation with HTTP 400 -//! `Eip1271SimulationFailed`. We trigger this by routing the simulation -//! through a tiny always-revert wrapper contract referenced from -//! `app_data.protocol.wrappers`. Reverts coming from a wrapper bypass the -//! `HooksTrampoline` revert-swallow, so the simulation actually fails. -//! - A well-formed Safe-signed order with no wrapper must be accepted (HTTP -//! 200), proving the adapter wiring lets healthy orders through. +//! - Negative: a Safe-signed order whose `app_data.protocol.wrappers` points +//! at a custom always-revert wrapper. With the orderbook in +//! `Eip1271SimulationMode::Enforce`, the simulation drives `settle()` +//! through the wrapper, the wrapper reverts, and the API rejects with +//! HTTP 400 `Eip1271SimulationFailed`. A wrapper is used rather than a +//! buggy pre-hook because `HooksTrampoline.execute` deliberately swallows +//! hook reverts, so a buggy hook would not surface as a simulation +//! failure. +//! - Positive: a Safe-signed order with empty app_data is accepted, proving +//! the adapter wiring lets healthy orders through. use { alloy::{ From c1aabeb4625b7bb2b8267ff33e7f459713faa963 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 06:55:11 +0000 Subject: [PATCH 072/154] more functionality needed for trade verification --- crates/simulator/src/simulation_builder.rs | 25 ++++++++++++++++++++- crates/simulator/src/simulation_encoding.rs | 11 +++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b7a20307e9..e4f1d21563 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -12,7 +12,7 @@ use { alloy_sol_types::SolCall, alloy_transport::RpcError, anyhow::{Context, Result}, - balance_overrides::BalanceOverriding, + balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, ethrpc::block_stream::CurrentBlockWatcher, model::{ DomainSeparator, @@ -82,9 +82,14 @@ impl SettlementSimulator { auction_id: None, state_overrides: StateOverride::default(), fund_settlement_contract: false, + fund_requests: vec![], block: Block::Latest, } } + + pub fn domain_separator(&self) -> DomainSeparator { + self.0.domain_separator + } } /// Which block to simulate against. @@ -110,6 +115,7 @@ pub struct SimulationBuilder { pub(crate) state_overrides: StateOverride, pub(crate) simulator: SettlementSimulator, pub(crate) fund_settlement_contract: bool, + pub(crate) fund_requests: Vec, pub(crate) block: Block, } @@ -236,6 +242,23 @@ impl SimulationBuilder { self } + /// Override the token balance of an arbitrary address. Useful for seeding + /// an intermediate funding contract (e.g. Spardose) with sell tokens before + /// the simulation runs. + pub fn fund_address_with_tokens( + mut self, + holder: Address, + token: Address, + amount: U256, + ) -> Self { + self.fund_requests.push(BalanceOverrideRequest { + token, + holder, + amount, + }); + self + } + /// Finishes the simulation struct based on the configuration thus far. pub async fn build(self) -> Result { self.build_with_modifications(|_| {}).await diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index a8edecbfab..0fd48b8a9d 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -167,6 +167,17 @@ pub(crate) async fn encode( state_overrides.insert(address, state_override); } + for request in builder.fund_requests { + let (address, account_override) = builder + .simulator + .0 + .balance_overrides + .state_override(request) + .await + .ok_or(BuildError::FailedToOverrideBalances)?; + state_overrides.insert(address, account_override); + } + Ok(EthCallInputs { request: TransactionRequest { from: Some(from), From 29c225466cf14f326851bff343c6345482ddb3f4 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 06:58:02 +0000 Subject: [PATCH 073/154] remove need for native token address in trade verification --- contracts/artifacts/Solver.json | 9 +--- contracts/artifacts/Trader.json | 9 +--- .../contracts-generated/solver/src/lib.rs | 41 ++++++------------- .../contracts-generated/trader/src/lib.rs | 41 ++++++------------- contracts/solidity/Solver.sol | 4 +- contracts/solidity/Trader.sol | 19 --------- .../src/trade_verifier/mod.rs | 1 - 7 files changed, 29 insertions(+), 95 deletions(-) diff --git a/contracts/artifacts/Solver.json b/contracts/artifacts/Solver.json index 94ba3bd7da..1a0068b09a 100644 --- a/contracts/artifacts/Solver.json +++ b/contracts/artifacts/Solver.json @@ -22,11 +22,6 @@ "name": "sellAmount", "type": "uint256" }, - { - "internalType": "address", - "name": "nativeToken", - "type": "address" - }, { "internalType": "address", "name": "spardose", @@ -101,8 +96,8 @@ "type": "function" } ], - "bytecode": "0x6080604052348015600e575f5ffd5b506109c58061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a", - "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a", + "bytecode": "0x6080604052348015600e575f5ffd5b506109ab8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a", "devdoc": { "methods": {} }, diff --git a/contracts/artifacts/Trader.json b/contracts/artifacts/Trader.json index 2bb2245e43..7a9cb937ef 100644 --- a/contracts/artifacts/Trader.json +++ b/contracts/artifacts/Trader.json @@ -21,11 +21,6 @@ "name": "sellAmount", "type": "uint256" }, - { - "internalType": "address", - "name": "nativeToken", - "type": "address" - }, { "internalType": "address", "name": "spardose", @@ -89,8 +84,8 @@ "type": "receive" } ], - "bytecode": "0x6080604052348015600e575f5ffd5b50610d008061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a", - "deployedBytecode": "0x608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a", + "bytecode": "0x6080604052348015600e575f5ffd5b50610baa8061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a", + "deployedBytecode": "0x608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a", "devdoc": { "methods": {} }, diff --git a/contracts/generated/contracts-generated/solver/src/lib.rs b/contracts/generated/contracts-generated/solver/src/lib.rs index cd27b738be..fbfe9aad21 100644 --- a/contracts/generated/contracts-generated/solver/src/lib.rs +++ b/contracts/generated/contracts-generated/solver/src/lib.rs @@ -11,7 +11,7 @@ Generated by the following Solidity interface... ```solidity interface Solver { - function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; + function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; function storeBalance(address token, address owner, bool countGas) external; function swap(address settlementContract, address[] memory tokens, address payable receiver, bytes memory settlementCall) external returns (uint256 gasUsed, uint256[] memory queriedBalances); } @@ -44,11 +44,6 @@ interface Solver { "type": "uint256", "internalType": "uint256" }, - { - "name": "nativeToken", - "type": "address", - "internalType": "address" - }, { "name": "spardose", "type": "address", @@ -134,27 +129,27 @@ pub mod Solver { /// The creation / init bytecode of the contract. /// /// ```text - ///0x6080604052348015600e575f5ffd5b506109c58061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a + ///0x6080604052348015600e575f5ffd5b506109ab8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\t\xC5\x80a\0\x1C_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c%\x82\xED\xB4\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x07\x02V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xC6V[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\x13V[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08\x88V[a\x03\x1EV[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x8DV[a\x01\xA1\x88\x85\x85a\x05WV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x8DV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7FT.\xB7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x81\x16`\x04\x83\x01R\x87\x81\x16`$\x83\x01R`D\x82\x01\x87\x90R\x85\x81\x16`d\x83\x01R\x84\x81\x16`\x84\x83\x01R\x91\x92P\x90\x88\x16\x90cT.\xB7}\x90`\xA4\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xB4W__\xFD[PZ\xF1\x15\x80\x15a\x02\xC6W=__>=_\xFD[PPPPZa\x02\xD5\x90\x82a\t\x01V[a\x02\xE1\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x10\x91\x90a\t\x1AV[\x90\x91UPPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x04\x07W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xDEW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\x02\x91\x90a\t-V[a\x04 V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04\x87WZa\x04F\x90\x82a\t\x01V[a\x04R\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04\x81\x91\x90a\t\x1AV[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04\x87W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xAFWa\x04\xAFa\tDV[\x90P` \x02\x01` \x81\x01\x90a\x04\xC4\x91\x90a\tqV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x055W__\xFD[PZ\xF1\x15\x80\x15a\x05GW=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x8F\x90PV[__Z\x90Pa\x05\xB2\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xF3V[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xE0\x90\x83a\t\x01V[a\x05\xEA\x91\x90a\t\x01V[\x95\x94PPPPPV[``a\x06\0\x83_\x84a\x06\x07V[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x060\x91\x90a\t\x8CV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06jW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06oV[``\x91P[P\x92P\x90P\x80a\x06\x81W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xAAW__\xFD[PV[\x805a\x06\xB8\x81a\x06\x89V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xCDW__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xE4W__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xFBW__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x17W__\xFD[\x865a\x07\"\x81a\x06\x89V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07=W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07MW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07cW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07wW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x8D`@\x88\x01a\x06\xADV[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\xA8W__\xFD[a\x07\xB4\x89\x82\x8A\x01a\x06\xBDV[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x08\x07W\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE9V[P\x90\x96\x95PPPPPPV[______`\xC0\x87\x89\x03\x12\x15a\x08(W__\xFD[\x865a\x083\x81a\x06\x89V[\x95P` \x87\x015a\x08C\x81a\x06\x89V[\x94P`@\x87\x015a\x08S\x81a\x06\x89V[\x93P``\x87\x015\x92P`\x80\x87\x015a\x08j\x81a\x06\x89V[\x91P`\xA0\x87\x015a\x08z\x81a\x06\x89V[\x80\x91PP\x92\x95P\x92\x95P\x92\x95V[___``\x84\x86\x03\x12\x15a\x08\x9AW__\xFD[\x835a\x08\xA5\x81a\x06\x89V[\x92P` \x84\x015a\x08\xB5\x81a\x06\x89V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xC9W__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[_` \x82\x84\x03\x12\x15a\t=W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\t\x81W__\xFD[\x815a\x06\0\x81a\x06\x89V[_\x82Q_[\x81\x81\x10\x15a\t\xABW` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\t\x91V[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\t\xAB\x80a\0\x1C_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c/\xF1\x11\xF5\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x06\xF9V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xBDV[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\nV[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08nV[a\x03\x15V[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x84V[a\x01\xA1\x88\x85\x85a\x05NV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x84V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7F[\xBE\x8B?\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x81\x16`\x04\x83\x01R\x86\x81\x16`$\x83\x01R`D\x82\x01\x86\x90R\x84\x81\x16`d\x83\x01R\x91\x92P\x90\x87\x16\x90c[\xBE\x8B?\x90`\x84\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xACW__\xFD[PZ\xF1\x15\x80\x15a\x02\xBEW=__>=_\xFD[PPPPZa\x02\xCD\x90\x82a\x08\xE7V[a\x02\xD9\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x08\x91\x90a\t\0V[\x90\x91UPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x03\xFEW`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xD5W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xF9\x91\x90a\t\x13V[a\x04\x17V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04~WZa\x04=\x90\x82a\x08\xE7V[a\x04I\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04x\x91\x90a\t\0V[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04~W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xA6Wa\x04\xA6a\t*V[\x90P` \x02\x01` \x81\x01\x90a\x04\xBB\x91\x90a\tWV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05,W__\xFD[PZ\xF1\x15\x80\x15a\x05>W=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x86\x90PV[__Z\x90Pa\x05\xA9\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xEAV[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xD7\x90\x83a\x08\xE7V[a\x05\xE1\x91\x90a\x08\xE7V[\x95\x94PPPPPV[``a\x05\xF7\x83_\x84a\x05\xFEV[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x06'\x91\x90a\trV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06aW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06fV[``\x91P[P\x92P\x90P\x80a\x06xW\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xA1W__\xFD[PV[\x805a\x06\xAF\x81a\x06\x80V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xC4W__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xDBW__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xF2W__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x0EW__\xFD[\x865a\x07\x19\x81a\x06\x80V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x074W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07DW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07ZW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07nW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x84`@\x88\x01a\x06\xA4V[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9FW__\xFD[a\x07\xAB\x89\x82\x8A\x01a\x06\xB4V[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x07\xFEW\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE0V[P\x90\x96\x95PPPPPPV[_____`\xA0\x86\x88\x03\x12\x15a\x08\x1EW__\xFD[\x855a\x08)\x81a\x06\x80V[\x94P` \x86\x015a\x089\x81a\x06\x80V[\x93P`@\x86\x015a\x08I\x81a\x06\x80V[\x92P``\x86\x015\x91P`\x80\x86\x015a\x08`\x81a\x06\x80V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x08\x80W__\xFD[\x835a\x08\x8B\x81a\x06\x80V[\x92P` \x84\x015a\x08\x9B\x81a\x06\x80V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xAFW__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[_` \x82\x84\x03\x12\x15a\t#W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\tgW__\xFD[\x815a\x05\xF7\x81a\x06\x80V[_\x82Q_[\x81\x81\x10\x15a\t\x91W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\twV[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", ); /// The runtime bytecode of the contract, as deployed on the network. /// /// ```text - ///0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a + ///0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c%\x82\xED\xB4\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x07\x02V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xC6V[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\x13V[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08\x88V[a\x03\x1EV[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x8DV[a\x01\xA1\x88\x85\x85a\x05WV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x8DV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7FT.\xB7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x81\x16`\x04\x83\x01R\x87\x81\x16`$\x83\x01R`D\x82\x01\x87\x90R\x85\x81\x16`d\x83\x01R\x84\x81\x16`\x84\x83\x01R\x91\x92P\x90\x88\x16\x90cT.\xB7}\x90`\xA4\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xB4W__\xFD[PZ\xF1\x15\x80\x15a\x02\xC6W=__>=_\xFD[PPPPZa\x02\xD5\x90\x82a\t\x01V[a\x02\xE1\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x10\x91\x90a\t\x1AV[\x90\x91UPPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x04\x07W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xDEW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\x02\x91\x90a\t-V[a\x04 V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04\x87WZa\x04F\x90\x82a\t\x01V[a\x04R\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04\x81\x91\x90a\t\x1AV[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04\x87W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xAFWa\x04\xAFa\tDV[\x90P` \x02\x01` \x81\x01\x90a\x04\xC4\x91\x90a\tqV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x055W__\xFD[PZ\xF1\x15\x80\x15a\x05GW=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x8F\x90PV[__Z\x90Pa\x05\xB2\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xF3V[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xE0\x90\x83a\t\x01V[a\x05\xEA\x91\x90a\t\x01V[\x95\x94PPPPPV[``a\x06\0\x83_\x84a\x06\x07V[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x060\x91\x90a\t\x8CV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06jW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06oV[``\x91P[P\x92P\x90P\x80a\x06\x81W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xAAW__\xFD[PV[\x805a\x06\xB8\x81a\x06\x89V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xCDW__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xE4W__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xFBW__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x17W__\xFD[\x865a\x07\"\x81a\x06\x89V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07=W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07MW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07cW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07wW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x8D`@\x88\x01a\x06\xADV[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\xA8W__\xFD[a\x07\xB4\x89\x82\x8A\x01a\x06\xBDV[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x08\x07W\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE9V[P\x90\x96\x95PPPPPPV[______`\xC0\x87\x89\x03\x12\x15a\x08(W__\xFD[\x865a\x083\x81a\x06\x89V[\x95P` \x87\x015a\x08C\x81a\x06\x89V[\x94P`@\x87\x015a\x08S\x81a\x06\x89V[\x93P``\x87\x015\x92P`\x80\x87\x015a\x08j\x81a\x06\x89V[\x91P`\xA0\x87\x015a\x08z\x81a\x06\x89V[\x80\x91PP\x92\x95P\x92\x95P\x92\x95V[___``\x84\x86\x03\x12\x15a\x08\x9AW__\xFD[\x835a\x08\xA5\x81a\x06\x89V[\x92P` \x84\x015a\x08\xB5\x81a\x06\x89V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xC9W__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[_` \x82\x84\x03\x12\x15a\t=W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\t\x81W__\xFD[\x815a\x06\0\x81a\x06\x89V[_\x82Q_[\x81\x81\x10\x15a\t\xABW` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\t\x91V[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c/\xF1\x11\xF5\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x06\xF9V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xBDV[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\nV[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08nV[a\x03\x15V[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x84V[a\x01\xA1\x88\x85\x85a\x05NV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x84V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7F[\xBE\x8B?\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x81\x16`\x04\x83\x01R\x86\x81\x16`$\x83\x01R`D\x82\x01\x86\x90R\x84\x81\x16`d\x83\x01R\x91\x92P\x90\x87\x16\x90c[\xBE\x8B?\x90`\x84\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xACW__\xFD[PZ\xF1\x15\x80\x15a\x02\xBEW=__>=_\xFD[PPPPZa\x02\xCD\x90\x82a\x08\xE7V[a\x02\xD9\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x08\x91\x90a\t\0V[\x90\x91UPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x03\xFEW`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xD5W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xF9\x91\x90a\t\x13V[a\x04\x17V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04~WZa\x04=\x90\x82a\x08\xE7V[a\x04I\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04x\x91\x90a\t\0V[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04~W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xA6Wa\x04\xA6a\t*V[\x90P` \x02\x01` \x81\x01\x90a\x04\xBB\x91\x90a\tWV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05,W__\xFD[PZ\xF1\x15\x80\x15a\x05>W=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x86\x90PV[__Z\x90Pa\x05\xA9\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xEAV[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xD7\x90\x83a\x08\xE7V[a\x05\xE1\x91\x90a\x08\xE7V[\x95\x94PPPPPV[``a\x05\xF7\x83_\x84a\x05\xFEV[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x06'\x91\x90a\trV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06aW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06fV[``\x91P[P\x92P\x90P\x80a\x06xW\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xA1W__\xFD[PV[\x805a\x06\xAF\x81a\x06\x80V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xC4W__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xDBW__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xF2W__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x0EW__\xFD[\x865a\x07\x19\x81a\x06\x80V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x074W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07DW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07ZW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07nW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x84`@\x88\x01a\x06\xA4V[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9FW__\xFD[a\x07\xAB\x89\x82\x8A\x01a\x06\xB4V[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x07\xFEW\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE0V[P\x90\x96\x95PPPPPPV[_____`\xA0\x86\x88\x03\x12\x15a\x08\x1EW__\xFD[\x855a\x08)\x81a\x06\x80V[\x94P` \x86\x015a\x089\x81a\x06\x80V[\x93P`@\x86\x015a\x08I\x81a\x06\x80V[\x92P``\x86\x015\x91P`\x80\x86\x015a\x08`\x81a\x06\x80V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x08\x80W__\xFD[\x835a\x08\x8B\x81a\x06\x80V[\x92P` \x84\x015a\x08\x9B\x81a\x06\x80V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xAFW__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[_` \x82\x84\x03\x12\x15a\t#W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\tgW__\xFD[\x815a\x05\xF7\x81a\x06\x80V[_\x82Q_[\x81\x81\x10\x15a\t\x91W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\twV[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", ); #[derive(Default, Debug, PartialEq, Eq, Hash)] - /**Function with signature `ensureTradePreconditions(address,address,address,uint256,address,address)` and selector `0x2582edb4`. + /**Function with signature `ensureTradePreconditions(address,address,address,uint256,address)` and selector `0x2ff111f5`. ```solidity - function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; + function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; ```*/ #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -168,12 +163,10 @@ pub mod Solver { #[allow(missing_docs)] pub sellAmount: alloy_sol_types::private::primitives::aliases::U256, #[allow(missing_docs)] - pub nativeToken: alloy_sol_types::private::Address, - #[allow(missing_docs)] pub spardose: alloy_sol_types::private::Address, } ///Container type for the return parameters of the - /// [`ensureTradePreconditions(address,address,address,uint256,address, + /// [`ensureTradePreconditions(address,address,address,uint256, /// address)`](ensureTradePreconditionsCall) function. #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -195,7 +188,6 @@ pub mod Solver { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, - alloy_sol_types::sol_data::Address, ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( @@ -204,7 +196,6 @@ pub mod Solver { alloy_sol_types::private::Address, alloy_sol_types::private::primitives::aliases::U256, alloy_sol_types::private::Address, - alloy_sol_types::private::Address, ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] @@ -224,7 +215,6 @@ pub mod Solver { value.settlementContract, value.sellToken, value.sellAmount, - value.nativeToken, value.spardose, ) } @@ -238,8 +228,7 @@ pub mod Solver { settlementContract: tuple.1, sellToken: tuple.2, sellAmount: tuple.3, - nativeToken: tuple.4, - spardose: tuple.5, + spardose: tuple.4, } } } @@ -290,16 +279,15 @@ pub mod Solver { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, - alloy_sol_types::sol_data::Address, ); type Return = ensureTradePreconditionsReturn; type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; type ReturnTuple<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SELECTOR: [u8; 4] = [37u8, 130u8, 237u8, 180u8]; + const SELECTOR: [u8; 4] = [47u8, 241u8, 17u8, 245u8]; const SIGNATURE: &'static str = - "ensureTradePreconditions(address,address,address,uint256,address,address)"; + "ensureTradePreconditions(address,address,address,uint256,address)"; #[inline] fn new<'a>( @@ -323,9 +311,6 @@ pub mod Solver { as alloy_sol_types::SolType>::tokenize( &self.sellAmount, ), - ::tokenize( - &self.nativeToken, - ), ::tokenize( &self.spardose, ), @@ -736,7 +721,7 @@ pub mod Solver { /// Prefer using `SolInterface` methods instead. pub const SELECTORS: &'static [[u8; 4usize]] = &[ [29u8, 71u8, 231u8, 244u8], - [37u8, 130u8, 237u8, 180u8], + [47u8, 241u8, 17u8, 245u8], [59u8, 187u8, 46u8, 29u8], ]; /// The signatures in the same order as `SELECTORS`. @@ -1083,7 +1068,6 @@ pub mod Solver { settlementContract: alloy_sol_types::private::Address, sellToken: alloy_sol_types::private::Address, sellAmount: alloy_sol_types::private::primitives::aliases::U256, - nativeToken: alloy_sol_types::private::Address, spardose: alloy_sol_types::private::Address, ) -> alloy_contract::SolCallBuilder<&P, ensureTradePreconditionsCall, N> { self.call_builder(&ensureTradePreconditionsCall { @@ -1091,7 +1075,6 @@ pub mod Solver { settlementContract, sellToken, sellAmount, - nativeToken, spardose, }) } diff --git a/contracts/generated/contracts-generated/trader/src/lib.rs b/contracts/generated/contracts-generated/trader/src/lib.rs index 9583bc614e..8fb9497e99 100644 --- a/contracts/generated/contracts-generated/trader/src/lib.rs +++ b/contracts/generated/contracts-generated/trader/src/lib.rs @@ -15,7 +15,7 @@ interface Trader { receive() external payable; - function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; + function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; function isValidSignature(bytes32, bytes memory) external pure returns (bytes4); function safeApprove(address token, address vaultRelayer, uint256 amount) external; } @@ -51,11 +51,6 @@ interface Trader { "type": "uint256", "internalType": "uint256" }, - { - "name": "nativeToken", - "type": "address", - "internalType": "address" - }, { "name": "spardose", "type": "address", @@ -126,27 +121,27 @@ pub mod Trader { /// The creation / init bytecode of the contract. /// /// ```text - ///0x6080604052348015600e575f5ffd5b50610d008061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a + ///0x6080604052348015600e575f5ffd5b50610baa8061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\r\0\x80a\0\x1C_9_\xF3\xFE`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80cT.\xB7}\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\x0B\x01V[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\x0B\x9CV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\x0C\0V[a\t\x16V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\x0C>V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[\x81s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x03a\x03\xE4W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03@W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03d\x91\x90a\x0CjV[\x90P\x83\x81\x10\x15a\x03\xE2W_a\x03y\x82\x86a\x0C\x81V[\x90P\x80G\x10a\x03\xE0W\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\xD0\xE3\r\xB0\x82`@Q\x82c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01_`@Q\x80\x83\x03\x81\x85\x88\x80;\x15\x80\x15a\x03\xC8W__\xFD[PZ\xF1\x15\x80\x15a\x03\xDAW=__>=_\xFD[PPPPP[P[P[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04.W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04R\x91\x90a\x0C\xB9V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04\xC7W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\xEB\x91\x90a\x0CjV[\x90P\x84\x81\x10\x15a\x07HW`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05gW__\xFD[PZ\xF1\x92PPP\x80\x15a\x05xWP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x06\x0BW__\xFD[PZ\xF1\x92PPP\x80\x15a\x06\x1CWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x88\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06\x90W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\xB4\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\x07FW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x07\xB2W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x07\xD6\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\t\x0CWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x88a\x08\x07\x84\x8Aa\x0C\x81V[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x08oW__\xFD[PZ\xF1\x92PPP\x80\x15a\x08\x80WP`\x01[a\t\x0CW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPPV[a\t7s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\tV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\n\xE2W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\n\xE7V[``\x91P[P\x92P\x90P\x80a\n\xF9W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\x0B\x13W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0B0W__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\x0B@W__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BVW__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\x0BgW__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x0B\x99W__\xFD[PV[_____`\xA0\x86\x88\x03\x12\x15a\x0B\xB0W__\xFD[\x855a\x0B\xBB\x81a\x0BxV[\x94P` \x86\x015a\x0B\xCB\x81a\x0BxV[\x93P`@\x86\x015\x92P``\x86\x015a\x0B\xE2\x81a\x0BxV[\x91P`\x80\x86\x015a\x0B\xF2\x81a\x0BxV[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x0C\x12W__\xFD[\x835a\x0C\x1D\x81a\x0BxV[\x92P` \x84\x015a\x0C-\x81a\x0BxV[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0C]W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\x0CCV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0CzW__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\nyW\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0C\xC9W__\xFD[\x81Qa\nS\x81a\x0BxV[_` \x82\x84\x03\x12\x15a\x0C\xE4W__\xFD[\x81Q\x80\x15\x15\x81\x14a\nSW__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\x0B\xAA\x80a\0\x1C_9_\xF3\xFE`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80c[\xBE\x8B?\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\t\xBFV[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\nZV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\n\xAAV[a\x07\xD4V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x02\xEDW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\x11\x91\x90a\x0B\x14V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x86\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\x86W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xAA\x91\x90a\x0B/V[\x90P\x83\x81\x10\x15a\x06\x07W`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04&W__\xFD[PZ\xF1\x92PPP\x80\x15a\x047WP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04\xCAW__\xFD[PZ\xF1\x92PPP\x80\x15a\x04\xDBWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x05OW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x05s\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x06\x05W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06qW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\x95\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x07\xCBWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x87a\x06\xC6\x84\x89a\x0BFV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x07.W__\xFD[PZ\xF1\x92PPP\x80\x15a\x07?WP`\x01[a\x07\xCBW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPV[a\x07\xF5s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\x07\xFAV[PPPV[`@\x80Qs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x81\x16`$\x83\x01R`D\x80\x83\x01\x85\x90R\x83Q\x80\x84\x03\x90\x91\x01\x81R`d\x90\x92\x01\x90\x92R` \x81\x01\x80Q{\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x7F\t^\xA7\xB3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x17\x90R\x90_\x90a\x08\x8C\x90\x86\x16\x83a\t\x04V[\x90Pa\x08\x97\x81a\t\x18V[a\x08\xFDW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`\x1A`$\x82\x01R\x7FSafeERC20: approval failed\0\0\0\0\0\0`D\x82\x01R`d\x01a\x02\x9AV[PPPPPV[``a\t\x11\x83_\x84a\t=V[\x93\x92PPPV[_\x81Q_\x14\x80a\t7WP\x81\x80` \x01\x90Q\x81\x01\x90a\t7\x91\x90a\x0B~V[\x92\x91PPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\tf\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\t\xA0W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\t\xA5V[``\x91P[P\x92P\x90P\x80a\t\xB7W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\t\xD1W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\t\xEEW__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\t\xFEW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\n\x14W__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\n%W__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\nWW__\xFD[PV[____`\x80\x85\x87\x03\x12\x15a\nmW__\xFD[\x845a\nx\x81a\n6V[\x93P` \x85\x015a\n\x88\x81a\n6V[\x92P`@\x85\x015\x91P``\x85\x015a\n\x9F\x81a\n6V[\x93\x96\x92\x95P\x90\x93PPV[___``\x84\x86\x03\x12\x15a\n\xBCW__\xFD[\x835a\n\xC7\x81a\n6V[\x92P` \x84\x015a\n\xD7\x81a\n6V[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0B\x07W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\n\xEDV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0B$W__\xFD[\x81Qa\t\x11\x81a\n6V[_` \x82\x84\x03\x12\x15a\x0B?W__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\t7W\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0B\x8EW__\xFD[\x81Q\x80\x15\x15\x81\x14a\t\x11W__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", ); /// The runtime bytecode of the contract, as deployed on the network. /// /// ```text - ///0x608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a + ///0x608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80cT.\xB7}\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\x0B\x01V[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\x0B\x9CV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\x0C\0V[a\t\x16V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\x0C>V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[\x81s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x03a\x03\xE4W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03@W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03d\x91\x90a\x0CjV[\x90P\x83\x81\x10\x15a\x03\xE2W_a\x03y\x82\x86a\x0C\x81V[\x90P\x80G\x10a\x03\xE0W\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\xD0\xE3\r\xB0\x82`@Q\x82c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01_`@Q\x80\x83\x03\x81\x85\x88\x80;\x15\x80\x15a\x03\xC8W__\xFD[PZ\xF1\x15\x80\x15a\x03\xDAW=__>=_\xFD[PPPPP[P[P[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04.W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04R\x91\x90a\x0C\xB9V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04\xC7W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\xEB\x91\x90a\x0CjV[\x90P\x84\x81\x10\x15a\x07HW`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05gW__\xFD[PZ\xF1\x92PPP\x80\x15a\x05xWP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x06\x0BW__\xFD[PZ\xF1\x92PPP\x80\x15a\x06\x1CWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x88\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06\x90W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\xB4\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\x07FW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x07\xB2W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x07\xD6\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\t\x0CWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x88a\x08\x07\x84\x8Aa\x0C\x81V[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x08oW__\xFD[PZ\xF1\x92PPP\x80\x15a\x08\x80WP`\x01[a\t\x0CW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPPV[a\t7s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\tV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\n\xE2W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\n\xE7V[``\x91P[P\x92P\x90P\x80a\n\xF9W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\x0B\x13W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0B0W__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\x0B@W__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BVW__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\x0BgW__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x0B\x99W__\xFD[PV[_____`\xA0\x86\x88\x03\x12\x15a\x0B\xB0W__\xFD[\x855a\x0B\xBB\x81a\x0BxV[\x94P` \x86\x015a\x0B\xCB\x81a\x0BxV[\x93P`@\x86\x015\x92P``\x86\x015a\x0B\xE2\x81a\x0BxV[\x91P`\x80\x86\x015a\x0B\xF2\x81a\x0BxV[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x0C\x12W__\xFD[\x835a\x0C\x1D\x81a\x0BxV[\x92P` \x84\x015a\x0C-\x81a\x0BxV[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0C]W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\x0CCV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0CzW__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\nyW\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0C\xC9W__\xFD[\x81Qa\nS\x81a\x0BxV[_` \x82\x84\x03\x12\x15a\x0C\xE4W__\xFD[\x81Q\x80\x15\x15\x81\x14a\nSW__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80c[\xBE\x8B?\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\t\xBFV[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\nZV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\n\xAAV[a\x07\xD4V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x02\xEDW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\x11\x91\x90a\x0B\x14V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x86\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\x86W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xAA\x91\x90a\x0B/V[\x90P\x83\x81\x10\x15a\x06\x07W`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04&W__\xFD[PZ\xF1\x92PPP\x80\x15a\x047WP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04\xCAW__\xFD[PZ\xF1\x92PPP\x80\x15a\x04\xDBWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x05OW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x05s\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x06\x05W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06qW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\x95\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x07\xCBWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x87a\x06\xC6\x84\x89a\x0BFV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x07.W__\xFD[PZ\xF1\x92PPP\x80\x15a\x07?WP`\x01[a\x07\xCBW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPV[a\x07\xF5s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\x07\xFAV[PPPV[`@\x80Qs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x81\x16`$\x83\x01R`D\x80\x83\x01\x85\x90R\x83Q\x80\x84\x03\x90\x91\x01\x81R`d\x90\x92\x01\x90\x92R` \x81\x01\x80Q{\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x7F\t^\xA7\xB3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x17\x90R\x90_\x90a\x08\x8C\x90\x86\x16\x83a\t\x04V[\x90Pa\x08\x97\x81a\t\x18V[a\x08\xFDW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`\x1A`$\x82\x01R\x7FSafeERC20: approval failed\0\0\0\0\0\0`D\x82\x01R`d\x01a\x02\x9AV[PPPPPV[``a\t\x11\x83_\x84a\t=V[\x93\x92PPPV[_\x81Q_\x14\x80a\t7WP\x81\x80` \x01\x90Q\x81\x01\x90a\t7\x91\x90a\x0B~V[\x92\x91PPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\tf\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\t\xA0W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\t\xA5V[``\x91P[P\x92P\x90P\x80a\t\xB7W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\t\xD1W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\t\xEEW__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\t\xFEW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\n\x14W__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\n%W__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\nWW__\xFD[PV[____`\x80\x85\x87\x03\x12\x15a\nmW__\xFD[\x845a\nx\x81a\n6V[\x93P` \x85\x015a\n\x88\x81a\n6V[\x92P`@\x85\x015\x91P``\x85\x015a\n\x9F\x81a\n6V[\x93\x96\x92\x95P\x90\x93PPV[___``\x84\x86\x03\x12\x15a\n\xBCW__\xFD[\x835a\n\xC7\x81a\n6V[\x92P` \x84\x015a\n\xD7\x81a\n6V[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0B\x07W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\n\xEDV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0B$W__\xFD[\x81Qa\t\x11\x81a\n6V[_` \x82\x84\x03\x12\x15a\x0B?W__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\t7W\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0B\x8EW__\xFD[\x81Q\x80\x15\x15\x81\x14a\t\x11W__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", ); #[derive(Default, Debug, PartialEq, Eq, Hash)] - /**Function with signature `ensureTradePreconditions(address,address,uint256,address,address)` and selector `0x542eb77d`. + /**Function with signature `ensureTradePreconditions(address,address,uint256,address)` and selector `0x5bbe8b3f`. ```solidity - function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; + function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; ```*/ #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -158,12 +153,10 @@ pub mod Trader { #[allow(missing_docs)] pub sellAmount: alloy_sol_types::private::primitives::aliases::U256, #[allow(missing_docs)] - pub nativeToken: alloy_sol_types::private::Address, - #[allow(missing_docs)] pub spardose: alloy_sol_types::private::Address, } ///Container type for the return parameters of the - /// [`ensureTradePreconditions(address,address,uint256,address, + /// [`ensureTradePreconditions(address,address,uint256, /// address)`](ensureTradePreconditionsCall) function. #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -184,7 +177,6 @@ pub mod Trader { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, - alloy_sol_types::sol_data::Address, ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( @@ -192,7 +184,6 @@ pub mod Trader { alloy_sol_types::private::Address, alloy_sol_types::private::primitives::aliases::U256, alloy_sol_types::private::Address, - alloy_sol_types::private::Address, ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] @@ -211,7 +202,6 @@ pub mod Trader { value.settlementContract, value.sellToken, value.sellAmount, - value.nativeToken, value.spardose, ) } @@ -224,8 +214,7 @@ pub mod Trader { settlementContract: tuple.0, sellToken: tuple.1, sellAmount: tuple.2, - nativeToken: tuple.3, - spardose: tuple.4, + spardose: tuple.3, } } } @@ -275,16 +264,15 @@ pub mod Trader { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, - alloy_sol_types::sol_data::Address, ); type Return = ensureTradePreconditionsReturn; type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; type ReturnTuple<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SELECTOR: [u8; 4] = [84u8, 46u8, 183u8, 125u8]; + const SELECTOR: [u8; 4] = [91u8, 190u8, 139u8, 63u8]; const SIGNATURE: &'static str = - "ensureTradePreconditions(address,address,uint256,address,address)"; + "ensureTradePreconditions(address,address,uint256,address)"; #[inline] fn new<'a>( @@ -305,9 +293,6 @@ pub mod Trader { as alloy_sol_types::SolType>::tokenize( &self.sellAmount, ), - ::tokenize( - &self.nativeToken, - ), ::tokenize( &self.spardose, ), @@ -679,7 +664,7 @@ pub mod Trader { /// Prefer using `SolInterface` methods instead. pub const SELECTORS: &'static [[u8; 4usize]] = &[ [22u8, 38u8, 186u8, 126u8], - [84u8, 46u8, 183u8, 125u8], + [91u8, 190u8, 139u8, 63u8], [235u8, 86u8, 37u8, 217u8], ]; /// The signatures in the same order as `SELECTORS`. @@ -1027,14 +1012,12 @@ pub mod Trader { settlementContract: alloy_sol_types::private::Address, sellToken: alloy_sol_types::private::Address, sellAmount: alloy_sol_types::private::primitives::aliases::U256, - nativeToken: alloy_sol_types::private::Address, spardose: alloy_sol_types::private::Address, ) -> alloy_contract::SolCallBuilder<&P, ensureTradePreconditionsCall, N> { self.call_builder(&ensureTradePreconditionsCall { settlementContract, sellToken, sellAmount, - nativeToken, spardose, }) } diff --git a/contracts/solidity/Solver.sol b/contracts/solidity/Solver.sol index b141f28653..0698e0a47d 100644 --- a/contracts/solidity/Solver.sol +++ b/contracts/solidity/Solver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; -import { IERC20, INativeERC20 } from "./interfaces/IERC20.sol"; +import { IERC20 } from "./interfaces/IERC20.sol"; import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol"; import { Caller } from "./libraries/Caller.sol"; import { Math } from "./libraries/Math.sol"; @@ -117,7 +117,6 @@ contract Solver layout at 0x14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff1 ISettlement settlementContract, address sellToken, uint256 sellAmount, - address nativeToken, address spardose ) external { uint256 gasStart = gasleft(); @@ -125,7 +124,6 @@ contract Solver layout at 0x14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff1 settlementContract, sellToken, sellAmount, - nativeToken, spardose ); // Account for costs of gas used outside of metered section. diff --git a/contracts/solidity/Trader.sol b/contracts/solidity/Trader.sol index f73e805ec9..be5dcdc158 100644 --- a/contracts/solidity/Trader.sol +++ b/contracts/solidity/Trader.sol @@ -55,34 +55,15 @@ contract Trader layout at 0x02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea5 /// a stable address in tests. /// @param sellToken - token being sold by the trade /// @param sellAmount - expected amount to be sold according to the quote - /// @param nativeToken - ERC20 version of the chain's native token /// @param spardose - piggy bank for requesting additional funds function ensureTradePreconditions( ISettlement settlementContract, address sellToken, uint256 sellAmount, - address nativeToken, address spardose ) external { require(!triggerInitialization(), "prepareSwap can only be called once"); - if (sellToken == nativeToken) { - uint256 availableNativeToken = IERC20(sellToken).balanceOf(address(this)); - if (availableNativeToken < sellAmount) { - uint256 amountToWrap = sellAmount - availableNativeToken; - // If the user has sufficient balance, simulate the wrapping the missing - // `ETH` so the user doesn't have to spend gas on that just to get a quote. - // If they are happy with the quote and want to create an order they will - // actually have to do the wrapping, though. Note that we don't attempt to - // wrap if the user doesn't have sufficient `ETH` balance, since that would - // revert. Instead, we fall-through so that we handle insufficient sell - // token balances uniformly for all tokens. - if (address(this).balance >= amountToWrap) { - INativeERC20(nativeToken).deposit{value: amountToWrap}(); - } - } - } - address vaultRelayer = settlementContract.vaultRelayer(); uint256 currentAllowance = IERC20(sellToken).allowance(address(this), vaultRelayer); if (currentAllowance < sellAmount) { diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 2bca6404e5..a96bcd1fb3 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -521,7 +521,6 @@ impl TradeVerifier { settlementContract: *self.settlement.address(), sellToken: query.sell_token, sellAmount: sell_amount, - nativeToken: self.simulator.native_token, spardose: Self::SPARDOSE, } .abi_encode(); From 9aec4950dbee8bf7aeee8f9c4d24961fe58d3805 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 08:41:13 +0000 Subject: [PATCH 074/154] Nicer API for handling state overrides --- crates/orderbook/src/orderbook.rs | 4 +- crates/simulator/src/lib.rs | 1 - crates/simulator/src/simulation_builder.rs | 92 ++++---- crates/simulator/src/simulation_encoding.rs | 208 ++++++++++++++---- .../simulator/src/state_override_helpers.rs | 68 ------ 5 files changed, 219 insertions(+), 154 deletions(-) delete mode 100644 crates/simulator/src/state_override_helpers.rs diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index d7fcad8bb8..acc718b694 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -652,7 +652,7 @@ impl Orderbook { .unwrap_or(simulation_builder::Block::Latest), ) .with_prices(simulation_builder::Prices::Limit) - .fund_settlement_contract_with_buy_tokens() + .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) .from_solver(simulation_builder::Solver::Fake(None)) .build() .await @@ -710,7 +710,7 @@ impl Orderbook { .unwrap_or(simulation_builder::Block::Latest), ) .with_prices(simulation_builder::Prices::Limit) - .fund_settlement_contract_with_buy_tokens() + .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) .from_solver(simulation_builder::Solver::Fake(None)) .build() .await diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index a0d66dd65f..2725227850 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -2,7 +2,6 @@ pub mod encoding; pub mod ethereum; pub mod simulation_builder; mod simulation_encoding; -pub mod state_override_helpers; pub mod swap_simulator; pub mod tenderly; mod utils; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index e4f1d21563..c51bc9073b 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -3,7 +3,7 @@ use { encoding::{EncodedSettlement, WrapperCall}, tenderly::dto::StateObject, }, - alloy_primitives::{Address, Bytes, TxKind, U256}, + alloy_primitives::{Address, B256, Bytes, TxKind, U256}, alloy_provider::{DynProvider, Provider}, alloy_rpc_types::{ TransactionRequest, @@ -12,7 +12,7 @@ use { alloy_sol_types::SolCall, alloy_transport::RpcError, anyhow::{Context, Result}, - balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, + balance_overrides::BalanceOverriding, ethrpc::block_stream::CurrentBlockWatcher, model::{ DomainSeparator, @@ -81,8 +81,7 @@ impl SettlementSimulator { solver: None, auction_id: None, state_overrides: StateOverride::default(), - fund_settlement_contract: false, - fund_requests: vec![], + account_override_requests: vec![], block: Block::Latest, } } @@ -114,8 +113,7 @@ pub struct SimulationBuilder { pub(crate) auction_id: Option, pub(crate) state_overrides: StateOverride, pub(crate) simulator: SettlementSimulator, - pub(crate) fund_settlement_contract: bool, - pub(crate) fund_requests: Vec, + pub(crate) account_override_requests: Vec, pub(crate) block: Block, } @@ -162,16 +160,6 @@ impl SimulationBuilder { self } - pub fn state_override( - mut self, - address: Address, - account_override: impl Into, - ) -> Self { - self.state_overrides - .insert(address, account_override.into()); - self - } - pub fn at_block(mut self, block: Block) -> Self { self.block = block; self @@ -234,28 +222,12 @@ impl SimulationBuilder { Ok(self) } - /// Override the settlement contract's buy token balance so it can pay out - /// the order without any external liquidity. The required amount is derived - /// from the order's executed amount and clearing prices at `build()` time. - pub fn fund_settlement_contract_with_buy_tokens(mut self) -> Self { - self.fund_settlement_contract = true; - self - } - - /// Override the token balance of an arbitrary address. Useful for seeding - /// an intermediate funding contract (e.g. Spardose) with sell tokens before - /// the simulation runs. - pub fn fund_address_with_tokens( - mut self, - holder: Address, - token: Address, - amount: U256, - ) -> Self { - self.fund_requests.push(BalanceOverrideRequest { - token, - holder, - amount, - }); + /// Queues an [`AccountOverrideRequest`] to be resolved and applied during + /// [`build`](Self::build). Multiple requests may target the same address; + /// non-conflicting fields are merged and conflicts produce + /// [`BuildError::ConflictingStateOverrides`]. + pub fn with_override(mut self, request: AccountOverrideRequest) -> Self { + self.account_override_requests.push(request); self } @@ -515,4 +487,48 @@ pub enum BuildError { AppDataParse(#[source] serde_json::Error), #[error("both wrappers and flashloans cannot be encoded in the same settlement")] FlashloanWrappersIncompatible, + #[error("conflicting state overrides for the same account: {0}")] + ConflictingStateOverrides(#[source] MergeConflict), +} + +pub enum AccountOverrideRequest { + /// Gives the address a huge amount of ETH. + SufficientEthBalance(Address), + /// Allowlists an address as a solver to let it settle orders. + AuthenticateAddress(Address), + /// Computes necessary state overrides for the requested balance. + Balance { + holder: Address, + token: Address, + amount: U256, + }, + /// Gives the settlement contract enough buy tokens to pay for all + /// orders. + BuyTokensForBuffers, + /// Deploys the provided code at the requested address. + Code { account: Address, code: Bytes }, + /// Allows to build fully custom overrides for the most exotic use cases. + Custom { + account: Address, + state: AccountOverride, + }, + // TODO: add Allowance +} + +/// Error returned when two [`AccountOverride`]s set the same field for the same +/// address and cannot be merged. +#[derive(Debug, thiserror::Error)] +pub enum MergeConflict { + #[error("both overrides set the ETH balance")] + Balance, + #[error("both overrides set the nonce")] + Nonce, + #[error("both overrides set the contract code")] + Code, + #[error("both overrides replace the full storage state")] + State, + #[error("overrides use incompatible storage strategies (state vs state_diff)")] + StateAndStateDiff, + #[error("both overrides write storage slot {0}")] + StateDiffSlot(B256), } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 0fd48b8a9d..0798ed7fb3 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -8,27 +8,31 @@ use { encode_wrapper_settlement, }, simulation_builder::{ + AccountOverrideRequest, Block, BuildError, EthCallInputs, ExecutionAmount, + MergeConflict, Order, Prices, SimulationBuilder, Solver, WrapperConfig, }, - state_override_helpers::{EthBalanceOverride, SolverAllowlisting}, }, - alloy_primitives::{Address, Bytes, U256}, - alloy_rpc_types::TransactionRequest, + alloy_primitives::{Address, B256, Bytes, U256, keccak256}, + alloy_rpc_types::{ + TransactionRequest, + state::{AccountOverride, StateOverride}, + }, alloy_sol_types::SolCall, balance_overrides::BalanceOverrideRequest, model::order::OrderKind, }; pub(crate) async fn encode( - builder: SimulationBuilder, + mut builder: SimulationBuilder, customize: impl FnOnce(&mut EncodedSettlement), ) -> Result { let order = builder.order.as_ref().ok_or(BuildError::NoOrder)?; @@ -65,18 +69,28 @@ pub(crate) async fn encode( .position(|t| *t == order.data.buy_token) .ok_or(BuildError::MissingBuyToken)?; - // Compute before clearing_prices is moved into EncodedSettlement below. - let fund_amount = builder.fund_settlement_contract.then(|| { - let base_amount = match order.data.kind { - OrderKind::Sell => clearing_prices[sell_token_index] - .saturating_mul(executed_amount) - .checked_div(clearing_prices[buy_token_index]) - .unwrap_or(U256::MAX), - OrderKind::Buy => executed_amount, - }; - // give 1 wei extra to avoid issues with rounding divisions - base_amount.saturating_add(U256::ONE) - }); + // Replace BuyTokensForBuffers placeholders with concrete Balance requests + // now that the required amounts are known. Must happen before clearing_prices + // is moved into EncodedSettlement. + for request in &mut builder.account_override_requests { + if matches!(request, AccountOverrideRequest::BuyTokensForBuffers) { + let amount = match order.data.kind { + OrderKind::Sell => clearing_prices[sell_token_index] + .saturating_mul(executed_amount) + .checked_div(clearing_prices[buy_token_index]) + .unwrap_or(U256::MAX), + OrderKind::Buy => executed_amount, + } + // give 1 wei extra to avoid issues with rounding divisions + .saturating_add(U256::ONE); + + *request = AccountOverrideRequest::Balance { + holder: *builder.simulator.0.settlement.address(), + token: order.data.buy_token, + amount, + }; + } + } let trade = encode_trade( &order.data, @@ -138,44 +152,72 @@ pub(crate) async fn encode( _ => (*builder.simulator.0.settlement.address(), settle_calldata), }; - let mut state_overrides = builder.state_overrides; let from = match builder.solver { Some(Solver::Real(addr)) => addr, Some(Solver::Fake(opt)) => { let addr = opt.unwrap_or_else(Address::random); - state_overrides.insert(addr, EthBalanceOverride(U256::MAX / U256::from(2)).into()); - state_overrides.insert( - builder.simulator.0.authenticator, - SolverAllowlisting(addr).into(), - ); + builder + .account_override_requests + .push(AccountOverrideRequest::SufficientEthBalance(addr)); + builder + .account_override_requests + .push(AccountOverrideRequest::AuthenticateAddress(addr)); addr } None => return Err(BuildError::NoSolver), }; - if let Some(amount) = fund_amount { - let (address, state_override) = builder - .simulator - .0 - .balance_overrides - .state_override(BalanceOverrideRequest { - token: order.data.buy_token, - holder: *builder.simulator.0.settlement.address(), + let mut state_overrides = builder.state_overrides; + for request in builder.account_override_requests { + let (address, account_override) = match request { + AccountOverrideRequest::SufficientEthBalance(addr) => ( + addr, + AccountOverride::default().with_balance(U256::MAX / U256::from(2)), + ), + AccountOverrideRequest::AuthenticateAddress(addr) => { + // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ + // slot_padded). + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(addr.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + let slot = keccak256(buf); + ( + builder.simulator.0.authenticator, + AccountOverride::default() + .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), + ) + } + AccountOverrideRequest::Balance { + holder, + token, amount, - }) - .await - .ok_or(BuildError::FailedToOverrideBalances)?; - state_overrides.insert(address, state_override); - } - - for request in builder.fund_requests { - let (address, account_override) = builder - .simulator - .0 - .balance_overrides - .state_override(request) - .await - .ok_or(BuildError::FailedToOverrideBalances)?; - state_overrides.insert(address, account_override); + } => builder + .simulator + .0 + .balance_overrides + .state_override(BalanceOverrideRequest { + token, + holder, + amount, + }) + .await + .ok_or(BuildError::FailedToOverrideBalances)?, + AccountOverrideRequest::BuyTokensForBuffers => { + unreachable!( + "replaced with specific Balance requests before state overrides get computed" + ) + } + AccountOverrideRequest::Code { account, code } => ( + account, + AccountOverride { + code: Some(code), + ..Default::default() + }, + ), + AccountOverrideRequest::Custom { account, state } => (account, state), + }; + apply_account_override(&mut state_overrides, address, account_override) + .map_err(BuildError::ConflictingStateOverrides)?; } Ok(EthCallInputs { @@ -221,3 +263,79 @@ async fn executed_amount( } }) } + +/// Merges `new` into `existing` field by field. +/// +/// Returns [`MergeConflict`] if both overrides write the same field. +/// Non-conflicting `state_diff` entries are combined into a single map. +fn merge_account_override( + existing: &mut AccountOverride, + new: AccountOverride, +) -> Result<(), MergeConflict> { + if new.balance.is_some() { + if existing.balance.is_some() { + return Err(MergeConflict::Balance); + } + existing.balance = new.balance; + } + if new.nonce.is_some() { + if existing.nonce.is_some() { + return Err(MergeConflict::Nonce); + } + existing.nonce = new.nonce; + } + if new.code.is_some() { + if existing.code.is_some() { + return Err(MergeConflict::Code); + } + existing.code = new.code; + } + match (new.state, new.state_diff) { + (Some(new_state), None) => { + if existing.state.is_some() { + return Err(MergeConflict::State); + } + if existing.state_diff.is_some() { + return Err(MergeConflict::StateAndStateDiff); + } + existing.state = Some(new_state); + } + (None, Some(new_diff)) => { + if existing.state.is_some() { + return Err(MergeConflict::StateAndStateDiff); + } + match &mut existing.state_diff { + None => existing.state_diff = Some(new_diff), + Some(existing_diff) => { + for (slot, value) in new_diff { + if existing_diff.contains_key(&slot) { + return Err(MergeConflict::StateDiffSlot(slot)); + } + existing_diff.insert(slot, value); + } + } + } + } + (None, None) => {} + // alloy does not allow both simultaneously, treat as incompatible + (Some(_), Some(_)) => return Err(MergeConflict::StateAndStateDiff), + } + Ok(()) +} + +/// Applies `new` to the override map for `address`. +/// +/// If `address` already has an entry, the overrides are merged via +/// [`merge_account_override`]. Returns an error on conflict. +pub fn apply_account_override( + overrides: &mut StateOverride, + address: Address, + new: AccountOverride, +) -> Result<(), MergeConflict> { + if let Some(existing) = overrides.get_mut(&address) { + merge_account_override(existing, new) + } else { + overrides.insert(address, new); + Ok(()) + } +} diff --git a/crates/simulator/src/state_override_helpers.rs b/crates/simulator/src/state_override_helpers.rs deleted file mode 100644 index b3aec19451..0000000000 --- a/crates/simulator/src/state_override_helpers.rs +++ /dev/null @@ -1,68 +0,0 @@ -use { - alloy_primitives::{Address, B256, Bytes, U256, keccak256}, - alloy_rpc_types::state::AccountOverride, - std::iter, -}; -pub use { - balance_overrides::{BalanceOverrideRequest, BalanceOverrides, BalanceOverriding}, - configs::balance_overrides::Strategy, -}; - -/// Deploys a fake ERC-1271 contract at a given address so that signature -/// verification succeeds unconditionally. Pass to -/// [`crate::simulation_builder::SimulationBuilder::state_override`] with the -/// order owner's address. -pub struct FakeUser; - -impl From for AccountOverride { - fn from(_fake_user: FakeUser) -> Self { - let code = Bytes::from_static(&[ - 0x63, 0x16, 0x26, 0xba, 0x7e, // PUSH4 0x1626ba7e - 0x60, 0xe0, // PUSH1 224 - 0x1b, // SHL → 0x1626ba7e left-aligned in 32-byte word - 0x60, 0x00, // PUSH1 0x00 - 0x52, // MSTORE - 0x60, 0x20, // PUSH1 0x20 (return 32 bytes) - 0x60, 0x00, // PUSH1 0x00 (from offset 0) - 0xf3, // RETURN - ]); - Self { - code: Some(code), - ..Default::default() - } - } -} - -/// Sets the ETH balance of an address to the given value. -pub struct EthBalanceOverride(pub U256); - -impl From for AccountOverride { - fn from(EthBalanceOverride(balance): EthBalanceOverride) -> Self { - Self { - balance: Some(balance), - ..Default::default() - } - } -} - -/// Overrides the authenticator contract's storage to allowlist a single solver -/// address. Pass to -/// [`crate::simulation_builder::SimulationBuilder::state_override`] -/// with the authenticator contract's address. -pub struct SolverAllowlisting(pub Address); - -impl From for AccountOverride { - fn from(SolverAllowlisting(solver): SolverAllowlisting) -> Self { - // GPv2AllowListAuthentication stores `mapping(address => bool) managers` - // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ - // slot_padded). - let mut buf = [0u8; 64]; - buf[12..32].copy_from_slice(solver.as_slice()); - buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); - let slot = keccak256(buf); - Self { - state_diff: Some(iter::once((slot, B256::with_last_byte(1))).collect()), - ..Default::default() - } - } -} From 0a092dc757f1fa98d396b239e4a12164aab3fd9f Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 08:54:47 +0000 Subject: [PATCH 075/154] build final state overrides in separate function --- Cargo.lock | 1 + crates/simulator/Cargo.toml | 1 + crates/simulator/src/simulation_builder.rs | 2 - crates/simulator/src/simulation_encoding.rs | 130 ++++++++++++-------- 4 files changed, 78 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92dfd80b0a..9596fd87fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7794,6 +7794,7 @@ dependencies = [ "derive_more 1.0.0", "eth-domain-types", "ethrpc", + "futures", "gas-price-estimation", "hex-literal", "http-client", diff --git a/crates/simulator/Cargo.toml b/crates/simulator/Cargo.toml index 3f5d900acd..1cb1960dfe 100644 --- a/crates/simulator/Cargo.toml +++ b/crates/simulator/Cargo.toml @@ -16,6 +16,7 @@ alloy-sol-types = { workspace = true } alloy-transport = { workspace = true } anyhow = { workspace = true } app-data = { workspace = true } +futures = { workspace = true } async-trait = { workspace = true } balance-overrides = { workspace = true } cached = { workspace = true } diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index c51bc9073b..e0770cfcb9 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -80,7 +80,6 @@ impl SettlementSimulator { prices: None, solver: None, auction_id: None, - state_overrides: StateOverride::default(), account_override_requests: vec![], block: Block::Latest, } @@ -111,7 +110,6 @@ pub struct SimulationBuilder { pub(crate) prices: Option, pub(crate) solver: Option, pub(crate) auction_id: Option, - pub(crate) state_overrides: StateOverride, pub(crate) simulator: SettlementSimulator, pub(crate) account_override_requests: Vec, pub(crate) block: Block, diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 0798ed7fb3..b96e344932 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -27,8 +27,9 @@ use { state::{AccountOverride, StateOverride}, }, alloy_sol_types::SolCall, - balance_overrides::BalanceOverrideRequest, + balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, model::order::OrderKind, + std::sync::Arc, }; pub(crate) async fn encode( @@ -166,59 +167,12 @@ pub(crate) async fn encode( } None => return Err(BuildError::NoSolver), }; - let mut state_overrides = builder.state_overrides; - for request in builder.account_override_requests { - let (address, account_override) = match request { - AccountOverrideRequest::SufficientEthBalance(addr) => ( - addr, - AccountOverride::default().with_balance(U256::MAX / U256::from(2)), - ), - AccountOverrideRequest::AuthenticateAddress(addr) => { - // GPv2AllowListAuthentication stores `mapping(address => bool) managers` - // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ - // slot_padded). - let mut buf = [0u8; 64]; - buf[12..32].copy_from_slice(addr.as_slice()); - buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); - let slot = keccak256(buf); - ( - builder.simulator.0.authenticator, - AccountOverride::default() - .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), - ) - } - AccountOverrideRequest::Balance { - holder, - token, - amount, - } => builder - .simulator - .0 - .balance_overrides - .state_override(BalanceOverrideRequest { - token, - holder, - amount, - }) - .await - .ok_or(BuildError::FailedToOverrideBalances)?, - AccountOverrideRequest::BuyTokensForBuffers => { - unreachable!( - "replaced with specific Balance requests before state overrides get computed" - ) - } - AccountOverrideRequest::Code { account, code } => ( - account, - AccountOverride { - code: Some(code), - ..Default::default() - }, - ), - AccountOverrideRequest::Custom { account, state } => (account, state), - }; - apply_account_override(&mut state_overrides, address, account_override) - .map_err(BuildError::ConflictingStateOverrides)?; - } + let state_overrides = build_final_state_overrides( + builder.account_override_requests, + Arc::clone(&builder.simulator.0.balance_overrides), + builder.simulator.0.authenticator, + ) + .await?; Ok(EthCallInputs { request: TransactionRequest { @@ -264,6 +218,74 @@ async fn executed_amount( }) } +/// Resolves all [`AccountOverrideRequest`]s concurrently, merges them +/// and returns the final [`StateOverride`]. +async fn build_final_state_overrides( + requests: Vec, + balance_overrides: Arc, + authenticator: Address, +) -> Result { + let futures = requests.into_iter().map(|request| { + let balance_overrides = Arc::clone(&balance_overrides); + async move { + match request { + AccountOverrideRequest::SufficientEthBalance(addr) => Ok(( + addr, + AccountOverride::default().with_balance(U256::MAX / U256::from(2)), + )), + AccountOverrideRequest::AuthenticateAddress(addr) => { + // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ + // slot_padded). + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(addr.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + let slot = keccak256(buf); + Ok(( + authenticator, + AccountOverride::default() + .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), + )) + } + AccountOverrideRequest::Balance { + holder, + token, + amount, + } => balance_overrides + .state_override(BalanceOverrideRequest { + token, + holder, + amount, + }) + .await + .ok_or(BuildError::FailedToOverrideBalances), + AccountOverrideRequest::BuyTokensForBuffers => { + unreachable!( + "replaced with specific Balance requests before state overrides get \ + computed" + ) + } + AccountOverrideRequest::Code { account, code } => Ok(( + account, + AccountOverride { + code: Some(code), + ..Default::default() + }, + )), + AccountOverrideRequest::Custom { account, state } => Ok((account, state)), + } + } + }); + let resolved_overrides = futures::future::try_join_all(futures).await?; + + let mut state_overrides = StateOverride::default(); + for (address, account_override) in resolved_overrides { + apply_account_override(&mut state_overrides, address, account_override) + .map_err(BuildError::ConflictingStateOverrides)?; + } + Ok(state_overrides) +} + /// Merges `new` into `existing` field by field. /// /// Returns [`MergeConflict`] if both overrides write the same field. From 58278bc0b1bc4a3f538e8e70eb6fe92f4255f70a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 11:04:56 +0100 Subject: [PATCH 076/154] Wire FlashLoanRouter address and add forked Aave replay test --- crates/e2e/tests/e2e/eip1271_aave_replay.rs | 123 ++++++++++++++++++ .../e2e/fixtures/aave_replay_app_data.json | 1 + .../e2e/fixtures/aave_replay_signature.hex | 1 + crates/e2e/tests/e2e/main.rs | 1 + crates/orderbook/src/run.rs | 5 +- 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 crates/e2e/tests/e2e/eip1271_aave_replay.rs create mode 100644 crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json create mode 100644 crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex diff --git a/crates/e2e/tests/e2e/eip1271_aave_replay.rs b/crates/e2e/tests/e2e/eip1271_aave_replay.rs new file mode 100644 index 0000000000..c40017be4d --- /dev/null +++ b/crates/e2e/tests/e2e/eip1271_aave_replay.rs @@ -0,0 +1,123 @@ +//! Forked-mainnet replay of a real Aave v3 debt-swap order. +//! +//! This test takes a production Aave debt-swap order that settled on mainnet +//! at block 24992052 and resubmits it through our orderbook running on a +//! mainnet fork at block 24992051 (one before settlement). The orderbook is +//! configured with `Eip1271SimulationMode::Enforce`, so the prototype's +//! validation-time simulation must accept the order for the API to return +//! HTTP 201. +//! +//! What this exercises end-to-end: +//! +//! - `WrapperConfig::Flashloan` routing through the deployed `FlashLoanRouter` +//! on the fork, against the real Aave v3 Pool as the lender. +//! - The user-signed pre-hook deploying the EIP-1167 helper clone via the +//! protocol-adapter factory and funding it with the loaned WETH. +//! - The signature_validator's pre-interaction simulation deploying the same +//! clone in time for `isValidSignature` to be called on real bytecode. +//! - The settlement transferring sell tokens from the now-funded helper. +//! - The post-hook running the loan repayment path. +//! +//! Order details (from cow API + DB): +//! uid: 0x7f5df255b55f5eba3034f74acb8e91a04aaf61a755b88c61ad7c61068856f3b2 +//! e58acb86761699c1cbc665e6b7e0271503f6336c69f323f8 +//! owner: 0xe58aCB86761699c1cBC665e6b7E0271503f6336C +//! sell: 4.473358935639875302 WETH +//! buy: 10003 GHO +//! class: limit, kind: buy, partiallyFillable: false +//! signed: eip1271 +//! appCode: aave-v3-interface-debt-swap +//! settled at block 24992052, status: fulfilled. + +use { + alloy::{ + hex, + primitives::{Address, U256, address}, + }, + configs::{orderbook::Eip1271SimulationMode, test_util::TestDefault}, + e2e::setup::{OnchainComponents, Services, run_forked_test_with_block_number}, + model::{ + order::{OrderCreation, OrderCreationAppData, OrderKind}, + signature::Signature, + }, + number::units::EthUnit, + shared::web3::Web3, + std::str::FromStr, +}; + +/// One block before the settlement transaction (24992052) of the replayed +/// order. +const FORK_BLOCK_MAINNET: u64 = 24992051; + +const ORDER_OWNER: Address = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); +const SELL_TOKEN_WETH: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); +const BUY_TOKEN_GHO: Address = address!("40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f"); +const SELL_AMOUNT: u128 = 4_473_358_935_639_875_302; +const VALID_TO: u32 = 1_777_542_136; +const BUY_AMOUNT_DECIMAL: &str = "10003000000000000000000"; + +const FULL_APP_DATA: &str = include_str!("fixtures/aave_replay_app_data.json"); +const SIGNATURE_HEX: &str = include_str!("fixtures/aave_replay_signature.hex"); + +#[tokio::test] +#[ignore] +async fn forked_node_mainnet_eip1271_aave_replay() { + run_forked_test_with_block_number( + forked_aave_replay, + std::env::var("FORK_URL_MAINNET").expect("FORK_URL_MAINNET must be set to run forked tests"), + FORK_BLOCK_MAINNET, + ) + .await; +} + +async fn forked_aave_replay(web3: Web3) { + let mut onchain = OnchainComponents::deployed(web3.clone()).await; + let [solver] = onchain.make_solvers_forked(1u64.eth()).await; + + let mut orderbook_config = configs::orderbook::Configuration::test_default(); + orderbook_config + .order_simulation + .as_mut() + .expect("test_default enables order_simulation") + .eip1271_simulation_mode = Eip1271SimulationMode::Enforce; + + let services = Services::new(&onchain).await; + services + .start_protocol_with_args( + configs::autopilot::Configuration::test("test_solver", solver.address()), + orderbook_config, + solver, + ) + .await; + + let signature_bytes = hex::decode(SIGNATURE_HEX.trim().trim_start_matches("0x")) + .expect("signature fixture must be valid hex"); + + let order = OrderCreation { + sell_token: SELL_TOKEN_WETH, + buy_token: BUY_TOKEN_GHO, + receiver: Some(ORDER_OWNER), + sell_amount: U256::from(SELL_AMOUNT), + buy_amount: U256::from_str(BUY_AMOUNT_DECIMAL).unwrap(), + valid_to: VALID_TO, + fee_amount: U256::ZERO, + kind: OrderKind::Buy, + partially_fillable: false, + from: Some(ORDER_OWNER), + signature: Signature::Eip1271(signature_bytes), + app_data: OrderCreationAppData::Full { + full: FULL_APP_DATA.trim().to_string(), + }, + ..Default::default() + }; + + let uid = services + .create_order(&order) + .await + .expect("orderbook should accept the replayed Aave order"); + tracing::info!(?uid, "order accepted"); + + let stored = services.get_order(&uid).await.unwrap(); + assert_eq!(stored.metadata.uid, uid); + assert_eq!(stored.metadata.owner, ORDER_OWNER); +} diff --git a/crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json b/crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json new file mode 100644 index 0000000000..7347f81364 --- /dev/null +++ b/crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json @@ -0,0 +1 @@ +{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"} \ No newline at end of file diff --git a/crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex b/crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex new file mode 100644 index 0000000000..9f8c61fa1f --- /dev/null +++ b/crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex @@ -0,0 +1 @@ +0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/crates/e2e/tests/e2e/main.rs b/crates/e2e/tests/e2e/main.rs index bf28f1c5fc..1b1fbbc055 100644 --- a/crates/e2e/tests/e2e/main.rs +++ b/crates/e2e/tests/e2e/main.rs @@ -16,6 +16,7 @@ mod cow_amm; mod database; mod debug_order; mod deprecated_endpoints; +mod eip1271_aave_replay; mod eip1271_creation_simulation; mod eip4626; mod eth_integration; diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index c2bf0dbea7..7765cc8033 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -390,9 +390,12 @@ pub async fn run(config: Configuration) { chain.id().to_string(), )) as _ }); + let flash_loan_router_address = + contracts::FlashLoanRouter::deployment_address(&chain.id()) + .unwrap_or_default(); let order_simulator = simulator::simulation_builder::SettlementSimulator::new( settlement_contract.clone(), - Default::default(), + flash_loan_router_address, hooks_trampoline_address, balance_overrider.clone(), current_block_stream.clone(), From ba042f01d64780f521cd9b9e3f7a12662750fdb8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 10:51:30 +0000 Subject: [PATCH 077/154] claude suggested changes --- crates/orderbook/src/orderbook.rs | 10 ++++- crates/orderbook/src/run.rs | 1 + .../src/trade_verifier/mod.rs | 2 + crates/simulator/src/simulation_builder.rs | 38 +++++++++++++++++-- crates/simulator/src/simulation_encoding.rs | 21 +++++++++- 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index acc718b694..5312b13814 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -642,7 +642,10 @@ impl Orderbook { .add_order( simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature) - .with_executed_amount(simulation_builder::ExecutionAmount::Remaining), + .fill_at( + simulation_builder::ExecutionAmount::Remaining, + simulation_builder::PriceEncoding::Exact, + ), ) .parameters_from_app_data(&full_app_data) .context("failed to parse app data")? @@ -699,7 +702,10 @@ impl Orderbook { partially_fillable: request.partially_fillable, }) .with_signature(request.owner, request.signature) - .with_executed_amount(simulation_builder::ExecutionAmount::Full), + .fill_at( + simulation_builder::ExecutionAmount::Full, + simulation_builder::PriceEncoding::Exact, + ), ) .parameters_from_app_data(&request.app_data) .context("failed to parse app data")? diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index ae9cdee469..08b3c67d55 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -424,6 +424,7 @@ pub async fn run(config: Configuration) { settlement_contract.clone(), Default::default(), hooks_trampoline_address, + *native_token.address(), balance_overrider.clone(), current_block_stream.clone(), tenderly, diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index a96bcd1fb3..1f6d0d0278 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -486,6 +486,8 @@ impl TradeVerifier { .call() .await .context("could not fetch authenticator")?; + // TODO: when switching to the `SettlementSimulator` use + // `StateOverrideRequest::AuthenticateAddress` for this overrides.insert( authenticator, AccountOverride { diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index e0770cfcb9..b69aa27e6c 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,6 +1,6 @@ use { crate::{ - encoding::{EncodedSettlement, WrapperCall}, + encoding::{EncodedSettlement, EncodedTrade, WrapperCall}, tenderly::dto::StateObject, }, alloy_primitives::{Address, B256, Bytes, TxKind, U256}, @@ -34,6 +34,7 @@ pub(crate) struct Inner { pub(crate) authenticator: Address, pub(crate) flash_loan_router: Address, pub(crate) hooks_trampoline: Address, + pub(crate) native_token: Address, pub(crate) balance_overrides: Arc, pub(crate) provider: DynProvider, pub(crate) domain_separator: DomainSeparator, @@ -47,6 +48,7 @@ impl SettlementSimulator { settlement: contracts::GPv2Settlement::Instance, flash_loan_router: Address, hooks_trampoline: Address, + native_token: Address, balance_overrides: Arc, current_block: CurrentBlockWatcher, tenderly: Option>, @@ -60,6 +62,7 @@ impl SettlementSimulator { authenticator, flash_loan_router, hooks_trampoline, + native_token, balance_overrides, provider, domain_separator, @@ -69,6 +72,10 @@ impl SettlementSimulator { }))) } + pub fn native_token(&self) -> Address { + self.0.native_token + } + pub fn new_simulation_builder(&self) -> SimulationBuilder { SimulationBuilder { simulator: self.clone(), @@ -81,6 +88,7 @@ impl SettlementSimulator { solver: None, auction_id: None, account_override_requests: vec![], + extra_trades: vec![], block: Block::Latest, } } @@ -112,6 +120,7 @@ pub struct SimulationBuilder { pub(crate) auction_id: Option, pub(crate) simulator: SettlementSimulator, pub(crate) account_override_requests: Vec, + pub(crate) extra_trades: Vec, pub(crate) block: Block, } @@ -229,6 +238,13 @@ impl SimulationBuilder { self } + /// Appends pre-encoded trades (e.g. JIT orders) to the settlement. + /// These are appended after the primary order's trade entry. + pub fn add_extra_trades(mut self, trades: Vec) -> Self { + self.extra_trades.extend(trades); + self + } + /// Finishes the simulation struct based on the configuration thus far. pub async fn build(self) -> Result { self.build_with_modifications(|_| {}).await @@ -290,6 +306,19 @@ pub enum ExecutionAmount { Explicit(U256), } +/// How the limit price of an order's trade entry should be encoded. +pub enum PriceEncoding { + /// Encode the exact sell_amount / buy_amount from the order as the limit + /// price. Default for production settlements. + Exact, + /// Set limit prices maximally permissive so the settlement always passes, + /// regardless of how many tokens the trader actually receives. Used for + /// quote verification so the actual out_amount can be measured afterward. + /// Sell orders: buy_amount = 0. Buy orders: sell_amount = max(sell_amount, + /// u128::MAX). + Disadvantageous, +} + /// A simulator-specific order that bundles the data needed to encode a trade. /// /// Construct with [`Order::new`] and add optional fields via the builder @@ -302,6 +331,7 @@ pub struct Order { pub(crate) pre_interactions: Vec, pub(crate) post_interactions: Vec, pub(crate) executed_amount: ExecutionAmount, + pub(crate) price_encoding: PriceEncoding, } /// Configuration for wrapping the settlement in a flashloan or custom wrapper @@ -328,6 +358,7 @@ impl Order { pre_interactions: vec![], post_interactions: vec![], executed_amount: ExecutionAmount::Remaining, + price_encoding: PriceEncoding::Exact, } } @@ -347,8 +378,9 @@ impl Order { self } - pub fn with_executed_amount(mut self, amount: ExecutionAmount) -> Self { - self.executed_amount = amount; + pub fn fill_at(mut self, execution: ExecutionAmount, price: PriceEncoding) -> Self { + self.executed_amount = execution; + self.price_encoding = price; self } } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index b96e344932..8652e8fac4 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -15,6 +15,7 @@ use { ExecutionAmount, MergeConflict, Order, + PriceEncoding, Prices, SimulationBuilder, Solver, @@ -93,8 +94,21 @@ pub(crate) async fn encode( } } + let trade_data = match order.price_encoding { + PriceEncoding::Exact => std::borrow::Cow::Borrowed(&order.data), + PriceEncoding::Disadvantageous => { + let mut d = order.data.clone(); + match d.kind { + model::order::OrderKind::Sell => d.buy_amount = U256::ZERO, + model::order::OrderKind::Buy => { + d.sell_amount = d.sell_amount.max(U256::from(u128::MAX)) + } + } + std::borrow::Cow::Owned(d) + } + }; let trade = encode_trade( - &order.data, + &trade_data, &order.signature, order.owner, sell_token_index, @@ -105,10 +119,13 @@ pub(crate) async fn encode( let order_pre = &order.pre_interactions; let order_post = &order.post_interactions; + let mut trades = vec![trade]; + trades.extend(builder.extra_trades); + let mut settlement = EncodedSettlement { tokens, clearing_prices, - trades: vec![trade], + trades, interactions: Interactions { // order's pre-hooks run before any additional pre-interactions pre: encode_interactions(order_pre.iter().chain(&builder.pre_interactions)), From 2cb914da8622b1aa69b626aad606cf7d7c695d81 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 12:41:33 +0100 Subject: [PATCH 078/154] Replace forked replay test with orderbook unit test pinned by RPC block --- crates/e2e/tests/e2e/eip1271_aave_replay.rs | 123 --------------- crates/e2e/tests/e2e/main.rs | 1 - crates/orderbook/tests/aave_replay.rs | 147 ++++++++++++++++++ .../tests}/fixtures/aave_replay_app_data.json | 2 +- .../tests}/fixtures/aave_replay_signature.hex | 2 +- 5 files changed, 149 insertions(+), 126 deletions(-) delete mode 100644 crates/e2e/tests/e2e/eip1271_aave_replay.rs create mode 100644 crates/orderbook/tests/aave_replay.rs rename crates/{e2e/tests/e2e => orderbook/tests}/fixtures/aave_replay_app_data.json (99%) rename crates/{e2e/tests/e2e => orderbook/tests}/fixtures/aave_replay_signature.hex (99%) diff --git a/crates/e2e/tests/e2e/eip1271_aave_replay.rs b/crates/e2e/tests/e2e/eip1271_aave_replay.rs deleted file mode 100644 index c40017be4d..0000000000 --- a/crates/e2e/tests/e2e/eip1271_aave_replay.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Forked-mainnet replay of a real Aave v3 debt-swap order. -//! -//! This test takes a production Aave debt-swap order that settled on mainnet -//! at block 24992052 and resubmits it through our orderbook running on a -//! mainnet fork at block 24992051 (one before settlement). The orderbook is -//! configured with `Eip1271SimulationMode::Enforce`, so the prototype's -//! validation-time simulation must accept the order for the API to return -//! HTTP 201. -//! -//! What this exercises end-to-end: -//! -//! - `WrapperConfig::Flashloan` routing through the deployed `FlashLoanRouter` -//! on the fork, against the real Aave v3 Pool as the lender. -//! - The user-signed pre-hook deploying the EIP-1167 helper clone via the -//! protocol-adapter factory and funding it with the loaned WETH. -//! - The signature_validator's pre-interaction simulation deploying the same -//! clone in time for `isValidSignature` to be called on real bytecode. -//! - The settlement transferring sell tokens from the now-funded helper. -//! - The post-hook running the loan repayment path. -//! -//! Order details (from cow API + DB): -//! uid: 0x7f5df255b55f5eba3034f74acb8e91a04aaf61a755b88c61ad7c61068856f3b2 -//! e58acb86761699c1cbc665e6b7e0271503f6336c69f323f8 -//! owner: 0xe58aCB86761699c1cBC665e6b7E0271503f6336C -//! sell: 4.473358935639875302 WETH -//! buy: 10003 GHO -//! class: limit, kind: buy, partiallyFillable: false -//! signed: eip1271 -//! appCode: aave-v3-interface-debt-swap -//! settled at block 24992052, status: fulfilled. - -use { - alloy::{ - hex, - primitives::{Address, U256, address}, - }, - configs::{orderbook::Eip1271SimulationMode, test_util::TestDefault}, - e2e::setup::{OnchainComponents, Services, run_forked_test_with_block_number}, - model::{ - order::{OrderCreation, OrderCreationAppData, OrderKind}, - signature::Signature, - }, - number::units::EthUnit, - shared::web3::Web3, - std::str::FromStr, -}; - -/// One block before the settlement transaction (24992052) of the replayed -/// order. -const FORK_BLOCK_MAINNET: u64 = 24992051; - -const ORDER_OWNER: Address = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); -const SELL_TOKEN_WETH: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); -const BUY_TOKEN_GHO: Address = address!("40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f"); -const SELL_AMOUNT: u128 = 4_473_358_935_639_875_302; -const VALID_TO: u32 = 1_777_542_136; -const BUY_AMOUNT_DECIMAL: &str = "10003000000000000000000"; - -const FULL_APP_DATA: &str = include_str!("fixtures/aave_replay_app_data.json"); -const SIGNATURE_HEX: &str = include_str!("fixtures/aave_replay_signature.hex"); - -#[tokio::test] -#[ignore] -async fn forked_node_mainnet_eip1271_aave_replay() { - run_forked_test_with_block_number( - forked_aave_replay, - std::env::var("FORK_URL_MAINNET").expect("FORK_URL_MAINNET must be set to run forked tests"), - FORK_BLOCK_MAINNET, - ) - .await; -} - -async fn forked_aave_replay(web3: Web3) { - let mut onchain = OnchainComponents::deployed(web3.clone()).await; - let [solver] = onchain.make_solvers_forked(1u64.eth()).await; - - let mut orderbook_config = configs::orderbook::Configuration::test_default(); - orderbook_config - .order_simulation - .as_mut() - .expect("test_default enables order_simulation") - .eip1271_simulation_mode = Eip1271SimulationMode::Enforce; - - let services = Services::new(&onchain).await; - services - .start_protocol_with_args( - configs::autopilot::Configuration::test("test_solver", solver.address()), - orderbook_config, - solver, - ) - .await; - - let signature_bytes = hex::decode(SIGNATURE_HEX.trim().trim_start_matches("0x")) - .expect("signature fixture must be valid hex"); - - let order = OrderCreation { - sell_token: SELL_TOKEN_WETH, - buy_token: BUY_TOKEN_GHO, - receiver: Some(ORDER_OWNER), - sell_amount: U256::from(SELL_AMOUNT), - buy_amount: U256::from_str(BUY_AMOUNT_DECIMAL).unwrap(), - valid_to: VALID_TO, - fee_amount: U256::ZERO, - kind: OrderKind::Buy, - partially_fillable: false, - from: Some(ORDER_OWNER), - signature: Signature::Eip1271(signature_bytes), - app_data: OrderCreationAppData::Full { - full: FULL_APP_DATA.trim().to_string(), - }, - ..Default::default() - }; - - let uid = services - .create_order(&order) - .await - .expect("orderbook should accept the replayed Aave order"); - tracing::info!(?uid, "order accepted"); - - let stored = services.get_order(&uid).await.unwrap(); - assert_eq!(stored.metadata.uid, uid); - assert_eq!(stored.metadata.owner, ORDER_OWNER); -} diff --git a/crates/e2e/tests/e2e/main.rs b/crates/e2e/tests/e2e/main.rs index 1b1fbbc055..bf28f1c5fc 100644 --- a/crates/e2e/tests/e2e/main.rs +++ b/crates/e2e/tests/e2e/main.rs @@ -16,7 +16,6 @@ mod cow_amm; mod database; mod debug_order; mod deprecated_endpoints; -mod eip1271_aave_replay; mod eip1271_creation_simulation; mod eip4626; mod eth_integration; diff --git a/crates/orderbook/tests/aave_replay.rs b/crates/orderbook/tests/aave_replay.rs new file mode 100644 index 0000000000..968d787faf --- /dev/null +++ b/crates/orderbook/tests/aave_replay.rs @@ -0,0 +1,147 @@ +//! Replay of a real Aave v3 debt-swap order against a historical mainnet +//! block, exercising the prototype's `SettlementSimulator`-based simulation +//! path end-to-end without involving the orderbook's wall-clock validity +//! check. +//! +//! Why this test exists: +//! +//! - A full `OrderValidator::validate_and_construct_order` flow uses +//! `SystemTime::now()` to bound `valid_to`, which makes any historical +//! order replay non-deterministic (the test rots as the order expires). +//! - The prototype's value lives in the simulation, not the validity check, +//! so we exercise the simulation directly: build a `SettlementSimulator` +//! against a real RPC, pin the simulation to the block right before +//! settlement, and assert it does not revert. +//! +//! Order replayed: an `aave-v3-interface-debt-swap` order +//! `0x7f5df255...69f323f8`, owner `0xe58aCB86...3f6336C` (an EIP-1167 minimal +//! proxy that the pre-hook deploys just-in-time), sell WETH, buy GHO, +//! settled at mainnet block 24992052. +//! +//! Run with `cargo nextest run --test aave_replay -p orderbook +//! --run-ignored ignored-only` and `MAINNET_RPC_URL` set to an archive node. +//! Without the env var, the test silently skips so CI without an archive +//! endpoint stays green. + +use { + alloy::primitives::{Address, U256, address}, + app_data::{AppDataHash, hash_full_app_data}, + model::{ + order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource}, + signature::Signature, + }, + simulator::simulation_builder::{ + self, + Block, + ExecutionAmount, + Prices, + SettlementSimulator, + Solver, + }, + std::{str::FromStr, sync::Arc}, +}; + +/// One block before the on-chain settlement transaction. At this block the +/// helper-clone owner contract has no code yet (the pre-hook deploys it), +/// the protocol-adapter factory is live, and Aave v3 has WETH liquidity. +const FORK_BLOCK_MAINNET: u64 = 24992051; + +const ORDER_OWNER: Address = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); +const SELL_TOKEN_WETH: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); +const BUY_TOKEN_GHO: Address = address!("40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f"); +const SELL_AMOUNT: u128 = 4_473_358_935_639_875_302; +const VALID_TO: u32 = 1_777_542_136; +const BUY_AMOUNT_DECIMAL: &str = "10003000000000000000000"; + +const FULL_APP_DATA: &str = include_str!("fixtures/aave_replay_app_data.json"); +const SIGNATURE_HEX: &str = include_str!("fixtures/aave_replay_signature.hex"); + +#[tokio::test] +#[ignore] +async fn aave_debt_swap_replay() { + let Ok(rpc_url) = std::env::var("MAINNET_RPC_URL") else { + eprintln!("MAINNET_RPC_URL not set - skipping replay test"); + return; + }; + + let web3 = ethrpc::Web3::new_from_url(&rpc_url); + let provider = web3.provider.clone(); + let chain_id = 1u64; + + let settlement = contracts::GPv2Settlement::Instance::deployed(&provider) + .await + .expect("settlement contract not deployed on mainnet?"); + + let flash_loan_router = contracts::FlashLoanRouter::deployment_address(&chain_id) + .expect("FlashLoanRouter deployment address"); + let hooks_trampoline = contracts::HooksTrampoline::deployment_address(&chain_id) + .expect("HooksTrampoline deployment address"); + + let balance_overrider = Arc::new(balance_overrides::BalanceOverrides::new(web3)); + let block_stream = ethrpc::block_stream::mock_single_block(Default::default()); + + let simulator = SettlementSimulator::new( + settlement, + flash_loan_router, + hooks_trampoline, + balance_overrider, + block_stream, + None, + ) + .await + .expect("failed to create SettlementSimulator"); + + let signature_bytes = alloy::primitives::hex::decode( + SIGNATURE_HEX.trim().trim_start_matches("0x"), + ) + .expect("signature fixture must be valid hex"); + + let app_data_json = FULL_APP_DATA.trim(); + let app_data_hash = AppDataHash(hash_full_app_data(app_data_json.as_bytes())); + + let order_data = OrderData { + sell_token: SELL_TOKEN_WETH, + buy_token: BUY_TOKEN_GHO, + receiver: Some(ORDER_OWNER), + sell_amount: U256::from(SELL_AMOUNT), + buy_amount: U256::from_str(BUY_AMOUNT_DECIMAL).unwrap(), + valid_to: VALID_TO, + app_data: app_data_hash, + fee_amount: U256::ZERO, + kind: OrderKind::Buy, + partially_fillable: false, + sell_token_balance: SellTokenSource::Erc20, + buy_token_balance: BuyTokenDestination::Erc20, + }; + + let inputs = simulator + .new_simulation_builder() + .add_order( + simulation_builder::Order::new(order_data) + .with_signature(ORDER_OWNER, Signature::Eip1271(signature_bytes)) + .with_executed_amount(ExecutionAmount::Full), + ) + .parameters_from_app_data(app_data_json) + .expect("parameters_from_app_data should parse the fixture") + .with_prices(Prices::Limit) + .from_solver(Solver::Fake(None)) + .fund_settlement_contract_with_buy_tokens() + .at_block(Block::Number(FORK_BLOCK_MAINNET)) + .build() + .await + .expect("failed to build simulation"); + + match inputs.simulate().await { + Ok(returndata) => { + tracing::info!( + "simulation succeeded, returndata: 0x{}", + alloy::primitives::hex::encode(&returndata) + ); + } + Err(err) => { + panic!( + "simulation must not revert for a healthy production order. Error: {err:?}" + ); + } + } +} diff --git a/crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json b/crates/orderbook/tests/fixtures/aave_replay_app_data.json similarity index 99% rename from crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json rename to crates/orderbook/tests/fixtures/aave_replay_app_data.json index 7347f81364..bb6b54d252 100644 --- a/crates/e2e/tests/e2e/fixtures/aave_replay_app_data.json +++ b/crates/orderbook/tests/fixtures/aave_replay_app_data.json @@ -1 +1 @@ -{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"} \ No newline at end of file +{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"} diff --git a/crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex b/crates/orderbook/tests/fixtures/aave_replay_signature.hex similarity index 99% rename from crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex rename to crates/orderbook/tests/fixtures/aave_replay_signature.hex index 9f8c61fa1f..6e4e030207 100644 --- a/crates/e2e/tests/e2e/fixtures/aave_replay_signature.hex +++ b/crates/orderbook/tests/fixtures/aave_replay_signature.hex @@ -1 +1 @@ -0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file +0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000 From 56d9bbc42f9e632c871b6edc20e94e00ca94759d Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 12:58:03 +0100 Subject: [PATCH 079/154] Tidy aave replay test: doc on test, locals over consts, simpler assert --- crates/orderbook/tests/aave_replay.rs | 119 +++++++++++--------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/crates/orderbook/tests/aave_replay.rs b/crates/orderbook/tests/aave_replay.rs index 968d787faf..a0432b2bf1 100644 --- a/crates/orderbook/tests/aave_replay.rs +++ b/crates/orderbook/tests/aave_replay.rs @@ -1,30 +1,5 @@ -//! Replay of a real Aave v3 debt-swap order against a historical mainnet -//! block, exercising the prototype's `SettlementSimulator`-based simulation -//! path end-to-end without involving the orderbook's wall-clock validity -//! check. -//! -//! Why this test exists: -//! -//! - A full `OrderValidator::validate_and_construct_order` flow uses -//! `SystemTime::now()` to bound `valid_to`, which makes any historical -//! order replay non-deterministic (the test rots as the order expires). -//! - The prototype's value lives in the simulation, not the validity check, -//! so we exercise the simulation directly: build a `SettlementSimulator` -//! against a real RPC, pin the simulation to the block right before -//! settlement, and assert it does not revert. -//! -//! Order replayed: an `aave-v3-interface-debt-swap` order -//! `0x7f5df255...69f323f8`, owner `0xe58aCB86...3f6336C` (an EIP-1167 minimal -//! proxy that the pre-hook deploys just-in-time), sell WETH, buy GHO, -//! settled at mainnet block 24992052. -//! -//! Run with `cargo nextest run --test aave_replay -p orderbook -//! --run-ignored ignored-only` and `MAINNET_RPC_URL` set to an archive node. -//! Without the env var, the test silently skips so CI without an archive -//! endpoint stays green. - use { - alloy::primitives::{Address, U256, address}, + alloy::primitives::{U256, address}, app_data::{AppDataHash, hash_full_app_data}, model::{ order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource}, @@ -41,21 +16,25 @@ use { std::{str::FromStr, sync::Arc}, }; -/// One block before the on-chain settlement transaction. At this block the -/// helper-clone owner contract has no code yet (the pre-hook deploys it), -/// the protocol-adapter factory is live, and Aave v3 has WETH liquidity. -const FORK_BLOCK_MAINNET: u64 = 24992051; - -const ORDER_OWNER: Address = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); -const SELL_TOKEN_WETH: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); -const BUY_TOKEN_GHO: Address = address!("40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f"); -const SELL_AMOUNT: u128 = 4_473_358_935_639_875_302; -const VALID_TO: u32 = 1_777_542_136; -const BUY_AMOUNT_DECIMAL: &str = "10003000000000000000000"; - -const FULL_APP_DATA: &str = include_str!("fixtures/aave_replay_app_data.json"); -const SIGNATURE_HEX: &str = include_str!("fixtures/aave_replay_signature.hex"); - +/// Replay of a real Aave v3 debt-swap order against a historical mainnet +/// block, exercising the prototype's `SettlementSimulator`-based simulation +/// path end-to-end without involving the orderbook's wall-clock validity +/// check. +/// +/// Why this test exists: +/// +/// - A full `OrderValidator::validate_and_construct_order` flow uses +/// `SystemTime::now()` to bound `valid_to`, which makes any historical +/// order replay non-deterministic (the test rots as the order expires). +/// - The prototype's value lives in the simulation, not the validity check, +/// so we exercise the simulation directly: build a `SettlementSimulator` +/// against a real RPC, pin the simulation to the block right before +/// settlement, and assert it does not revert. +/// +/// Order replayed: an `aave-v3-interface-debt-swap` order +/// `0x7f5df255b55f5eba3034f74acb8e91a04aaf61a755b88c61ad7c61068856f3b2e58acb86761699c1cbc665e6b7e0271503f6336c69f323f8`, +/// sell WETH, buy GHO. The owner is an EIP-1167 minimal proxy that the +/// pre-hook deploys just-in-time. #[tokio::test] #[ignore] async fn aave_debt_swap_replay() { @@ -64,6 +43,19 @@ async fn aave_debt_swap_replay() { return; }; + // One block before the on-chain settlement transaction. At this block the + // helper-clone owner contract has no code yet (the pre-hook deploys it), + // the protocol-adapter factory is live, and Aave v3 has WETH liquidity. + let fork_block_mainnet = 24_992_051u64; + let order_owner = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); + let sell_token_weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let buy_token_gho = address!("40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f"); + let sell_amount = U256::from_str("4473358935639875302").unwrap(); + let buy_amount = U256::from_str("10003000000000000000000").unwrap(); + let valid_to = 1_777_542_136u32; // 2026-04-30 09:42:16 UTC + let full_app_data = include_str!("fixtures/aave_replay_app_data.json").trim(); + let signature_hex = include_str!("fixtures/aave_replay_signature.hex").trim(); + let web3 = ethrpc::Web3::new_from_url(&rpc_url); let provider = web3.provider.clone(); let chain_id = 1u64; @@ -91,21 +83,17 @@ async fn aave_debt_swap_replay() { .await .expect("failed to create SettlementSimulator"); - let signature_bytes = alloy::primitives::hex::decode( - SIGNATURE_HEX.trim().trim_start_matches("0x"), - ) - .expect("signature fixture must be valid hex"); - - let app_data_json = FULL_APP_DATA.trim(); - let app_data_hash = AppDataHash(hash_full_app_data(app_data_json.as_bytes())); + let signature_bytes = alloy::primitives::hex::decode(signature_hex.trim_start_matches("0x")) + .expect("signature fixture must be valid hex"); + let app_data_hash = AppDataHash(hash_full_app_data(full_app_data.as_bytes())); let order_data = OrderData { - sell_token: SELL_TOKEN_WETH, - buy_token: BUY_TOKEN_GHO, - receiver: Some(ORDER_OWNER), - sell_amount: U256::from(SELL_AMOUNT), - buy_amount: U256::from_str(BUY_AMOUNT_DECIMAL).unwrap(), - valid_to: VALID_TO, + sell_token: sell_token_weth, + buy_token: buy_token_gho, + receiver: Some(order_owner), + sell_amount, + buy_amount, + valid_to, app_data: app_data_hash, fee_amount: U256::ZERO, kind: OrderKind::Buy, @@ -118,30 +106,21 @@ async fn aave_debt_swap_replay() { .new_simulation_builder() .add_order( simulation_builder::Order::new(order_data) - .with_signature(ORDER_OWNER, Signature::Eip1271(signature_bytes)) + .with_signature(order_owner, Signature::Eip1271(signature_bytes)) .with_executed_amount(ExecutionAmount::Full), ) - .parameters_from_app_data(app_data_json) + .parameters_from_app_data(full_app_data) .expect("parameters_from_app_data should parse the fixture") .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) .fund_settlement_contract_with_buy_tokens() - .at_block(Block::Number(FORK_BLOCK_MAINNET)) + .at_block(Block::Number(fork_block_mainnet)) .build() .await .expect("failed to build simulation"); - match inputs.simulate().await { - Ok(returndata) => { - tracing::info!( - "simulation succeeded, returndata: 0x{}", - alloy::primitives::hex::encode(&returndata) - ); - } - Err(err) => { - panic!( - "simulation must not revert for a healthy production order. Error: {err:?}" - ); - } - } + inputs + .simulate() + .await + .expect("simulation must not revert for a healthy production order"); } From f490a72c1bc3f1919c0cc96a60dc529d5fbe5b9a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:03:06 +0100 Subject: [PATCH 080/154] Add negative replay test for over-subscribed Aave flashloan --- crates/orderbook/tests/aave_replay.rs | 69 ++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/crates/orderbook/tests/aave_replay.rs b/crates/orderbook/tests/aave_replay.rs index a0432b2bf1..e691eec39c 100644 --- a/crates/orderbook/tests/aave_replay.rs +++ b/crates/orderbook/tests/aave_replay.rs @@ -8,6 +8,7 @@ use { simulator::simulation_builder::{ self, Block, + EthCallInputs, ExecutionAmount, Prices, SettlementSimulator, @@ -43,9 +44,57 @@ async fn aave_debt_swap_replay() { return; }; - // One block before the on-chain settlement transaction. At this block the - // helper-clone owner contract has no code yet (the pre-hook deploys it), - // the protocol-adapter factory is live, and Aave v3 has WETH liquidity. + let app_data = include_str!("fixtures/aave_replay_app_data.json").trim(); + let inputs = build_replay_simulation(&rpc_url, app_data).await; + + inputs + .simulate() + .await + .expect("simulation must not revert for a healthy production order"); +} + +/// Same order, but the `flashloan.amount` in `app_data` is rewritten to a +/// value Aave's WETH pool cannot satisfy. The wrapper call to the Aave Pool +/// must revert, and the simulation must propagate that revert. +/// +/// This proves the prototype actually executes the flashloan path: if the +/// wrapper call were a silent no-op (e.g. wrong router address), the +/// simulation would not depend on Aave's liquidity at all and would not +/// fail here. +#[tokio::test] +#[ignore] +async fn aave_debt_swap_replay_fails_when_flashloan_oversubscribed() { + let Ok(rpc_url) = std::env::var("MAINNET_RPC_URL") else { + eprintln!("MAINNET_RPC_URL not set - skipping replay test"); + return; + }; + + let original = include_str!("fixtures/aave_replay_app_data.json").trim(); + let mut value: serde_json::Value = + serde_json::from_str(original).expect("fixture must be valid JSON"); + // Way more WETH than Aave can lend. Aave reverts with insufficient + // liquidity (or similar) before any settlement runs. + value["metadata"]["flashloan"]["amount"] = + serde_json::Value::String(U256::MAX.to_string()); + let tampered_app_data = serde_json::to_string(&value).unwrap(); + + let inputs = build_replay_simulation(&rpc_url, &tampered_app_data).await; + + let err = inputs + .simulate() + .await + .expect_err("simulation must revert when the flashloan exceeds Aave liquidity"); + tracing::info!(?err, "expected simulation revert observed"); +} + +/// Builds a simulation pinned to the block right before the Aave debt-swap +/// settlement. The caller controls `full_app_data` so the same wiring +/// supports a positive replay (untouched) and a negative replay (tampered). +async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallInputs { + // One block before the on-chain settlement transaction. At this block + // the helper-clone owner contract has no code yet (the pre-hook deploys + // it), the protocol-adapter factory is live, and Aave v3 has WETH + // liquidity. let fork_block_mainnet = 24_992_051u64; let order_owner = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); let sell_token_weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); @@ -53,10 +102,9 @@ async fn aave_debt_swap_replay() { let sell_amount = U256::from_str("4473358935639875302").unwrap(); let buy_amount = U256::from_str("10003000000000000000000").unwrap(); let valid_to = 1_777_542_136u32; // 2026-04-30 09:42:16 UTC - let full_app_data = include_str!("fixtures/aave_replay_app_data.json").trim(); let signature_hex = include_str!("fixtures/aave_replay_signature.hex").trim(); - let web3 = ethrpc::Web3::new_from_url(&rpc_url); + let web3 = ethrpc::Web3::new_from_url(rpc_url); let provider = web3.provider.clone(); let chain_id = 1u64; @@ -102,7 +150,7 @@ async fn aave_debt_swap_replay() { buy_token_balance: BuyTokenDestination::Erc20, }; - let inputs = simulator + simulator .new_simulation_builder() .add_order( simulation_builder::Order::new(order_data) @@ -110,17 +158,12 @@ async fn aave_debt_swap_replay() { .with_executed_amount(ExecutionAmount::Full), ) .parameters_from_app_data(full_app_data) - .expect("parameters_from_app_data should parse the fixture") + .expect("parameters_from_app_data should parse the app data") .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) .fund_settlement_contract_with_buy_tokens() .at_block(Block::Number(fork_block_mainnet)) .build() .await - .expect("failed to build simulation"); - - inputs - .simulate() - .await - .expect("simulation must not revert for a healthy production order"); + .expect("failed to build simulation") } From 72382936f45439b3952570ee9651c523538e823c Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:10:00 +0100 Subject: [PATCH 081/154] Move Aave replay test from orderbook to simulator crate --- crates/{orderbook => simulator}/tests/aave_replay.rs | 4 ++-- .../tests/fixtures/aave_replay_app_data.json | 0 .../tests/fixtures/aave_replay_signature.hex | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/{orderbook => simulator}/tests/aave_replay.rs (98%) rename crates/{orderbook => simulator}/tests/fixtures/aave_replay_app_data.json (100%) rename crates/{orderbook => simulator}/tests/fixtures/aave_replay_signature.hex (100%) diff --git a/crates/orderbook/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs similarity index 98% rename from crates/orderbook/tests/aave_replay.rs rename to crates/simulator/tests/aave_replay.rs index e691eec39c..6790c9d1d0 100644 --- a/crates/orderbook/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -1,5 +1,5 @@ use { - alloy::primitives::{U256, address}, + alloy_primitives::{U256, address, hex}, app_data::{AppDataHash, hash_full_app_data}, model::{ order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource}, @@ -131,7 +131,7 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI .await .expect("failed to create SettlementSimulator"); - let signature_bytes = alloy::primitives::hex::decode(signature_hex.trim_start_matches("0x")) + let signature_bytes = hex::decode(signature_hex.trim_start_matches("0x")) .expect("signature fixture must be valid hex"); let app_data_hash = AppDataHash(hash_full_app_data(full_app_data.as_bytes())); diff --git a/crates/orderbook/tests/fixtures/aave_replay_app_data.json b/crates/simulator/tests/fixtures/aave_replay_app_data.json similarity index 100% rename from crates/orderbook/tests/fixtures/aave_replay_app_data.json rename to crates/simulator/tests/fixtures/aave_replay_app_data.json diff --git a/crates/orderbook/tests/fixtures/aave_replay_signature.hex b/crates/simulator/tests/fixtures/aave_replay_signature.hex similarity index 100% rename from crates/orderbook/tests/fixtures/aave_replay_signature.hex rename to crates/simulator/tests/fixtures/aave_replay_signature.hex From f92ed6bdf8fc18e8ccd662bd679b1454f243bc99 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:16:57 +0100 Subject: [PATCH 082/154] Inline aave-replay fixtures as constants, drop fixture files --- crates/simulator/tests/aave_replay.rs | 22 +++++++++++-------- .../tests/fixtures/aave_replay_app_data.json | 1 - .../tests/fixtures/aave_replay_signature.hex | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) delete mode 100644 crates/simulator/tests/fixtures/aave_replay_app_data.json delete mode 100644 crates/simulator/tests/fixtures/aave_replay_signature.hex diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 6790c9d1d0..df79d2b14e 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -17,6 +17,14 @@ use { std::{str::FromStr, sync::Arc}, }; +/// Full `app_data` JSON the trader signed for the replayed Aave v3 debt-swap +/// order. +const APP_DATA: &str = r#"{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; + +/// Production EIP-1271 signature blob for the replayed order. The trader's +/// signer contract decodes it and validates against the order hash. +const SIGNATURE_HEX: &str = "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000"; + /// Replay of a real Aave v3 debt-swap order against a historical mainnet /// block, exercising the prototype's `SettlementSimulator`-based simulation /// path end-to-end without involving the orderbook's wall-clock validity @@ -44,8 +52,7 @@ async fn aave_debt_swap_replay() { return; }; - let app_data = include_str!("fixtures/aave_replay_app_data.json").trim(); - let inputs = build_replay_simulation(&rpc_url, app_data).await; + let inputs = build_replay_simulation(&rpc_url, APP_DATA).await; inputs .simulate() @@ -69,13 +76,11 @@ async fn aave_debt_swap_replay_fails_when_flashloan_oversubscribed() { return; }; - let original = include_str!("fixtures/aave_replay_app_data.json").trim(); let mut value: serde_json::Value = - serde_json::from_str(original).expect("fixture must be valid JSON"); + serde_json::from_str(APP_DATA).expect("APP_DATA must be valid JSON"); // Way more WETH than Aave can lend. Aave reverts with insufficient // liquidity (or similar) before any settlement runs. - value["metadata"]["flashloan"]["amount"] = - serde_json::Value::String(U256::MAX.to_string()); + value["metadata"]["flashloan"]["amount"] = serde_json::Value::String(U256::MAX.to_string()); let tampered_app_data = serde_json::to_string(&value).unwrap(); let inputs = build_replay_simulation(&rpc_url, &tampered_app_data).await; @@ -102,7 +107,6 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI let sell_amount = U256::from_str("4473358935639875302").unwrap(); let buy_amount = U256::from_str("10003000000000000000000").unwrap(); let valid_to = 1_777_542_136u32; // 2026-04-30 09:42:16 UTC - let signature_hex = include_str!("fixtures/aave_replay_signature.hex").trim(); let web3 = ethrpc::Web3::new_from_url(rpc_url); let provider = web3.provider.clone(); @@ -131,8 +135,8 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI .await .expect("failed to create SettlementSimulator"); - let signature_bytes = hex::decode(signature_hex.trim_start_matches("0x")) - .expect("signature fixture must be valid hex"); + let signature_bytes = hex::decode(SIGNATURE_HEX.trim_start_matches("0x")) + .expect("SIGNATURE_HEX must be valid hex"); let app_data_hash = AppDataHash(hash_full_app_data(full_app_data.as_bytes())); let order_data = OrderData { diff --git a/crates/simulator/tests/fixtures/aave_replay_app_data.json b/crates/simulator/tests/fixtures/aave_replay_app_data.json deleted file mode 100644 index bb6b54d252..0000000000 --- a/crates/simulator/tests/fixtures/aave_replay_app_data.json +++ /dev/null @@ -1 +0,0 @@ -{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"} diff --git a/crates/simulator/tests/fixtures/aave_replay_signature.hex b/crates/simulator/tests/fixtures/aave_replay_signature.hex deleted file mode 100644 index 6e4e030207..0000000000 --- a/crates/simulator/tests/fixtures/aave_replay_signature.hex +++ /dev/null @@ -1 +0,0 @@ -0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000 From f60519708809be4493269d05aa74828867f06fff Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:19:54 +0100 Subject: [PATCH 083/154] Split APP_DATA via concat! and drop redundant info log --- crates/simulator/tests/aave_replay.rs | 47 ++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index df79d2b14e..49cd6e86e4 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -18,8 +18,48 @@ use { }; /// Full `app_data` JSON the trader signed for the replayed Aave v3 debt-swap -/// order. -const APP_DATA: &str = r#"{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; +/// order. Byte-equivalent to the production payload, broken up via `concat!` +/// for readability. Do not change the bytes: the EIP-1271 signature embeds +/// `keccak256(APP_DATA)` and the signer contract validates against it. +const APP_DATA: &str = concat!( + r#"{"#, + r#""appCode":"aave-v3-interface-debt-swap","#, + r#""metadata":{"#, + r#""flashloan":{"#, + r#""amount":"4475596734006878742","#, + r#""liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","#, + r#""protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","#, + r#""receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","#, + r#""token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2""#, + r#"},"#, + r#""hooks":{"#, + r#""post":[{"#, + r#""callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","#, + r#""dappId":"cow-sdk://flashloans/aave/v3/debt-swap","#, + r#""gasLimit":"1000000","#, + r#""target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C""#, + r#"}],"#, + r#""pre":[{"#, + r#""callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","#, + r#""dappId":"cow-sdk://flashloans/aave/v3/debt-swap","#, + r#""gasLimit":"300000","#, + r#""target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192""#, + r#"}]"#, + r#"},"#, + r#""orderClass":{"orderClass":"market"},"#, + r#""partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"#, + r#""quote":{"slippageBips":140,"smartSlippage":true},"#, + r#""utm":{"#, + r#""utmCampaign":"developer-cohort","#, + r#""utmContent":"","#, + r#""utmMedium":"cow-sdk@7.3.4","#, + r#""utmSource":"cowmunity","#, + r#""utmTerm":"js""#, + r#"}"#, + r#"},"#, + r#""version":"1.14.0""#, + r#"}"#, +); /// Production EIP-1271 signature blob for the replayed order. The trader's /// signer contract decodes it and validates against the order hash. @@ -85,11 +125,10 @@ async fn aave_debt_swap_replay_fails_when_flashloan_oversubscribed() { let inputs = build_replay_simulation(&rpc_url, &tampered_app_data).await; - let err = inputs + inputs .simulate() .await .expect_err("simulation must revert when the flashloan exceeds Aave liquidity"); - tracing::info!(?err, "expected simulation revert observed"); } /// Builds a simulation pinned to the block right before the Aave debt-swap From 1e8ddaa9fe8c00177f54e01b70bc3a683cc3aac8 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:22:37 +0100 Subject: [PATCH 084/154] Use single r-string for APP_DATA instead of concat! ladder --- crates/simulator/tests/aave_replay.rs | 46 +++------------------------ 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 49cd6e86e4..0579734c99 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -18,48 +18,10 @@ use { }; /// Full `app_data` JSON the trader signed for the replayed Aave v3 debt-swap -/// order. Byte-equivalent to the production payload, broken up via `concat!` -/// for readability. Do not change the bytes: the EIP-1271 signature embeds -/// `keccak256(APP_DATA)` and the signer contract validates against it. -const APP_DATA: &str = concat!( - r#"{"#, - r#""appCode":"aave-v3-interface-debt-swap","#, - r#""metadata":{"#, - r#""flashloan":{"#, - r#""amount":"4475596734006878742","#, - r#""liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","#, - r#""protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","#, - r#""receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","#, - r#""token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2""#, - r#"},"#, - r#""hooks":{"#, - r#""post":[{"#, - r#""callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","#, - r#""dappId":"cow-sdk://flashloans/aave/v3/debt-swap","#, - r#""gasLimit":"1000000","#, - r#""target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C""#, - r#"}],"#, - r#""pre":[{"#, - r#""callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","#, - r#""dappId":"cow-sdk://flashloans/aave/v3/debt-swap","#, - r#""gasLimit":"300000","#, - r#""target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192""#, - r#"}]"#, - r#"},"#, - r#""orderClass":{"orderClass":"market"},"#, - r#""partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"#, - r#""quote":{"slippageBips":140,"smartSlippage":true},"#, - r#""utm":{"#, - r#""utmCampaign":"developer-cohort","#, - r#""utmContent":"","#, - r#""utmMedium":"cow-sdk@7.3.4","#, - r#""utmSource":"cowmunity","#, - r#""utmTerm":"js""#, - r#"}"#, - r#"},"#, - r#""version":"1.14.0""#, - r#"}"#, -); +/// order. Do not edit: the EIP-1271 signature embeds `keccak256(APP_DATA)` +/// and the signer contract validates against it, so any byte change here +/// invalidates the signature. +const APP_DATA: &str = r#"{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; /// Production EIP-1271 signature blob for the replayed order. The trader's /// signer contract decodes it and validates against the order hash. From 1794027754fd5b55ed875991ce274d4fee630321 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:25:19 +0100 Subject: [PATCH 085/154] Format APP_DATA multi-line and canonicalise via serde_json before use --- crates/simulator/tests/aave_replay.rs | 59 ++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 0579734c99..6c61a5c6e6 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -18,10 +18,58 @@ use { }; /// Full `app_data` JSON the trader signed for the replayed Aave v3 debt-swap -/// order. Do not edit: the EIP-1271 signature embeds `keccak256(APP_DATA)` -/// and the signer contract validates against it, so any byte change here -/// invalidates the signature. -const APP_DATA: &str = r#"{"appCode":"aave-v3-interface-debt-swap","metadata":{"flashloan":{"amount":"4475596734006878742","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},"hooks":{"post":[{"callData":"0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"1000000","target":"0xe58aCB86761699c1cBC665e6b7E0271503f6336C"}],"pre":[{"callData":"0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000","dappId":"cow-sdk://flashloans/aave/v3/debt-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"market"},"partnerFee":{"recipient":"0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c","volumeBps":0},"quote":{"slippageBips":140,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; +/// order. Whitespace here is for readability only - tests canonicalise via +/// `serde_json` (which sorts keys alphabetically and strips whitespace) +/// before hashing or passing the JSON downstream, and the production order's +/// field ordering is already alphabetical at every level so the canonical +/// form matches the signed bytes. +const APP_DATA: &str = r#"{ + "appCode": "aave-v3-interface-debt-swap", + "metadata": { + "flashloan": { + "amount": "4475596734006878742", + "liquidityProvider": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", + "protocolAdapter": "0xdeCC46a4b09162F5369c5C80383AAa9159bCf192", + "receiver": "0xdeCC46a4b09162F5369c5C80383AAa9159bCf192", + "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "hooks": { + "post": [{ + "callData": "0xad3da559000000000000000000000000000000000000000000000000444d51cbc68377680000000000000000000000000000000000000000000000000000000069f323f8000000000000000000000000000000000000000000000000000000000000001c445675473b3e0941842eb5405ec1d9cb93c7d64b513b30d928d7ea42067440cb00fbafa80085d754964d5d70f616d899049f4e75c240fda7c2f108a99c882e8b", + "dappId": "cow-sdk://flashloans/aave/v3/debt-swap", + "gasLimit": "1000000", + "target": "0xe58aCB86761699c1cBC665e6b7E0271503f6336C" + }], + "pre": [{ + "callData": "0xb1b6308b00000000000000000000000073e7af13ef172f13d8fefebfd90c7a65300963440000000000000000000000006276ac03090f2bb8be680178343ac368f713b4e8000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc0000000000000000000000000000000000000000000000000000000069f323f80000000000000000000000000000000000000000000000003e1c83845060e2160000000000000000000000000000000000000000000000000007f34408be83300000000000000000000000000000000000000000000000003e1c83845060e21600000000000000000000000000000000000000000000021e4382edd5a86c0000", + "dappId": "cow-sdk://flashloans/aave/v3/debt-swap", + "gasLimit": "300000", + "target": "0xdeCC46a4b09162F5369c5C80383AAa9159bCf192" + }] + }, + "orderClass": {"orderClass": "market"}, + "partnerFee": {"recipient": "0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c", "volumeBps": 0}, + "quote": {"slippageBips": 140, "smartSlippage": true}, + "utm": { + "utmCampaign": "developer-cohort", + "utmContent": "", + "utmMedium": "cow-sdk@7.3.4", + "utmSource": "cowmunity", + "utmTerm": "js" + } + }, + "version": "1.14.0" +}"#; + +/// Re-serialises `app_data` JSON to its canonical (minified, alphabetically +/// keyed) form. `serde_json`'s default `Map` is a `BTreeMap`, so the output +/// is deterministic and matches the production payload byte-for-byte (which +/// is itself alphabetically keyed at every level). +fn canonicalise_app_data(app_data: &str) -> String { + let value: serde_json::Value = + serde_json::from_str(app_data).expect("APP_DATA must be valid JSON"); + serde_json::to_string(&value).expect("re-serialising must succeed") +} /// Production EIP-1271 signature blob for the replayed order. The trader's /// signer contract decodes it and validates against the order hash. @@ -54,7 +102,8 @@ async fn aave_debt_swap_replay() { return; }; - let inputs = build_replay_simulation(&rpc_url, APP_DATA).await; + let canonical_app_data = canonicalise_app_data(APP_DATA); + let inputs = build_replay_simulation(&rpc_url, &canonical_app_data).await; inputs .simulate() From 42da4d242581a8de27b27110dcb962a8a6b10214 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:28:10 +0100 Subject: [PATCH 086/154] Fmt --- .../tests/e2e/eip1271_creation_simulation.rs | 19 +++++++++---------- crates/orderbook/src/run.rs | 3 +-- crates/simulator/tests/aave_replay.rs | 12 ++++++------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index aac27a6657..4046a8a9d4 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -1,15 +1,14 @@ //! Local-node tests for the EIP-1271 creation-time simulation. //! -//! - Negative: a Safe-signed order whose `app_data.protocol.wrappers` points -//! at a custom always-revert wrapper. With the orderbook in -//! `Eip1271SimulationMode::Enforce`, the simulation drives `settle()` -//! through the wrapper, the wrapper reverts, and the API rejects with -//! HTTP 400 `Eip1271SimulationFailed`. A wrapper is used rather than a -//! buggy pre-hook because `HooksTrampoline.execute` deliberately swallows -//! hook reverts, so a buggy hook would not surface as a simulation -//! failure. -//! - Positive: a Safe-signed order with empty app_data is accepted, proving -//! the adapter wiring lets healthy orders through. +//! - Negative: a Safe-signed order whose `app_data.protocol.wrappers` points at +//! a custom always-revert wrapper. With the orderbook in +//! `Eip1271SimulationMode::Enforce`, the simulation drives `settle()` through +//! the wrapper, the wrapper reverts, and the API rejects with HTTP 400 +//! `Eip1271SimulationFailed`. A wrapper is used rather than a buggy pre-hook +//! because `HooksTrampoline.execute` deliberately swallows hook reverts, so a +//! buggy hook would not surface as a simulation failure. +//! - Positive: a Safe-signed order with empty app_data is accepted, proving the +//! adapter wiring lets healthy orders through. use { alloy::{ diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 7765cc8033..120fb10734 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -391,8 +391,7 @@ pub async fn run(config: Configuration) { )) as _ }); let flash_loan_router_address = - contracts::FlashLoanRouter::deployment_address(&chain.id()) - .unwrap_or_default(); + contracts::FlashLoanRouter::deployment_address(&chain.id()).unwrap_or_default(); let order_simulator = simulator::simulation_builder::SettlementSimulator::new( settlement_contract.clone(), flash_loan_router_address, diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 6c61a5c6e6..1d70759359 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -83,12 +83,12 @@ const SIGNATURE_HEX: &str = "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f2 /// Why this test exists: /// /// - A full `OrderValidator::validate_and_construct_order` flow uses -/// `SystemTime::now()` to bound `valid_to`, which makes any historical -/// order replay non-deterministic (the test rots as the order expires). -/// - The prototype's value lives in the simulation, not the validity check, -/// so we exercise the simulation directly: build a `SettlementSimulator` -/// against a real RPC, pin the simulation to the block right before -/// settlement, and assert it does not revert. +/// `SystemTime::now()` to bound `valid_to`, which makes any historical order +/// replay non-deterministic (the test rots as the order expires). +/// - The prototype's value lives in the simulation, not the validity check, so +/// we exercise the simulation directly: build a `SettlementSimulator` against +/// a real RPC, pin the simulation to the block right before settlement, and +/// assert it does not revert. /// /// Order replayed: an `aave-v3-interface-debt-swap` order /// `0x7f5df255b55f5eba3034f74acb8e91a04aaf61a755b88c61ad7c61068856f3b2e58acb86761699c1cbc665e6b7e0271503f6336c69f323f8`, From 7c4aab832f703b336868bbe4cc685c78b1500b87 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:34:30 +0100 Subject: [PATCH 087/154] Tighten APP_DATA / canonicalise_app_data doc comments --- crates/simulator/tests/aave_replay.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 1d70759359..b760b14eeb 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -18,11 +18,8 @@ use { }; /// Full `app_data` JSON the trader signed for the replayed Aave v3 debt-swap -/// order. Whitespace here is for readability only - tests canonicalise via -/// `serde_json` (which sorts keys alphabetically and strips whitespace) -/// before hashing or passing the JSON downstream, and the production order's -/// field ordering is already alphabetical at every level so the canonical -/// form matches the signed bytes. +/// order. Source-level whitespace is for readability only - run through +/// `canonicalise_app_data` before hashing or passing downstream. const APP_DATA: &str = r#"{ "appCode": "aave-v3-interface-debt-swap", "metadata": { @@ -61,10 +58,9 @@ const APP_DATA: &str = r#"{ "version": "1.14.0" }"#; -/// Re-serialises `app_data` JSON to its canonical (minified, alphabetically -/// keyed) form. `serde_json`'s default `Map` is a `BTreeMap`, so the output -/// is deterministic and matches the production payload byte-for-byte (which -/// is itself alphabetically keyed at every level). +/// Returns `app_data` minified with keys sorted alphabetically. The output +/// matches the signed production bytes byte-for-byte because that payload +/// is already alphabetically keyed at every level. fn canonicalise_app_data(app_data: &str) -> String { let value: serde_json::Value = serde_json::from_str(app_data).expect("APP_DATA must be valid JSON"); From 2ea57753214d0a5bbe2ac1ac0c77fe61e1cb5501 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 13:36:35 +0100 Subject: [PATCH 088/154] Assert negative test errors with EVM revert, not just any Err --- crates/simulator/tests/aave_replay.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index b760b14eeb..547af4453e 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -132,10 +132,15 @@ async fn aave_debt_swap_replay_fails_when_flashloan_oversubscribed() { let inputs = build_replay_simulation(&rpc_url, &tampered_app_data).await; - inputs + let err = inputs .simulate() .await .expect_err("simulation must revert when the flashloan exceeds Aave liquidity"); + let msg = format!("{err:?}"); + assert!( + msg.contains("execution reverted"), + "expected an EVM revert, got: {msg}", + ); } /// Builds a simulation pinned to the block right before the Aave debt-swap From bfc562762b838169578d04de17fd7649bf171ff3 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 12:51:35 +0000 Subject: [PATCH 089/154] use `SettlementSimulator` in trade verification --- crates/e2e/tests/e2e/quote_verification.rs | 28 +- crates/orderbook/src/orderbook.rs | 6 +- crates/price-estimation/src/factory.rs | 25 +- .../src/trade_verifier/mod.rs | 300 ++++++++++-------- crates/simulator/src/simulation_builder.rs | 64 ++-- crates/simulator/src/simulation_encoding.rs | 36 +-- 6 files changed, 232 insertions(+), 227 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index a7561b96ad..6d1f5aa749 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -12,7 +12,7 @@ use { price_estimation::BalanceOverridesConfig, test_util::TestDefault, }, - contracts::ERC20, + contracts::{ERC20, GPv2Settlement}, e2e::setup::*, ethrpc::{Web3, alloy::CallBuilderExt}, model::{ @@ -28,7 +28,7 @@ use { trade_verifier::{PriceQuery, TradeVerifier, TradeVerifying}, }, serde_json::json, - simulator::swap_simulator::SwapSimulator, + simulator::simulation_builder::SettlementSimulator, std::{collections::HashMap, sync::Arc}, }; @@ -189,32 +189,34 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { .unwrap(); let onchain = OnchainComponents::deployed(web3.clone()).await; let balance_overrides = Arc::new(BalanceOverrides::default()); - let gas_limit = 12_000_000; - let simulator = SwapSimulator::new( - balance_overrides.clone(), + let gas_limit = 12_000_000u64; + let settlement_contract = GPv2Settlement::GPv2Settlement::new( *onchain.contracts().gp_settlement.address(), + web3.provider.clone(), + ); + let simulator = SettlementSimulator::new( + settlement_contract, + Default::default(), + Default::default(), *onchain.contracts().weth.address(), + balance_overrides.clone(), block_stream.clone(), - web3.clone(), - gas_limit, + None, ) .await .unwrap(); let verifier = TradeVerifier::new( - web3.clone(), - None, simulator, + gas_limit, + None, Arc::new(web3.clone()), balance_overrides, - *onchain.contracts().gp_settlement.address(), BigDecimal::zero(), Default::default(), 0, u32::MAX, - ) - .await - .unwrap(); + ); let verify_trade = |tx_origin| { let verifier = verifier.clone(); diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 5312b13814..afb38f4bbd 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -644,7 +644,7 @@ impl Orderbook { .with_signature(order.metadata.owner, order.signature) .fill_at( simulation_builder::ExecutionAmount::Remaining, - simulation_builder::PriceEncoding::Exact, + simulation_builder::PriceEncoding::LimitPrice, ), ) .parameters_from_app_data(&full_app_data) @@ -654,7 +654,6 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_prices(simulation_builder::Prices::Limit) .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) .from_solver(simulation_builder::Solver::Fake(None)) .build() @@ -704,7 +703,7 @@ impl Orderbook { .with_signature(request.owner, request.signature) .fill_at( simulation_builder::ExecutionAmount::Full, - simulation_builder::PriceEncoding::Exact, + simulation_builder::PriceEncoding::LimitPrice, ), ) .parameters_from_app_data(&request.app_data) @@ -715,7 +714,6 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_prices(simulation_builder::Prices::Limit) .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) .from_solver(simulation_builder::Solver::Fake(None)) .build() diff --git a/crates/price-estimation/src/factory.rs b/crates/price-estimation/src/factory.rs index 6dc1e9c971..cafa889f0d 100644 --- a/crates/price-estimation/src/factory.rs +++ b/crates/price-estimation/src/factory.rs @@ -21,14 +21,14 @@ use { anyhow::{Context as _, Result}, bad_tokens::list_based::DenyListedTokens, configs::price_estimation::PriceEstimation, - contracts::WETH9, + contracts::{GPv2Settlement, WETH9}, ethrpc::{Web3, alloy::ProviderLabelingExt, block_stream::CurrentBlockWatcher}, gas_price_estimation::GasPriceEstimating, http_client::HttpClientFactory, number::nonzero::NonZeroU256, rate_limit::RateLimiter, reqwest::Url, - simulator::{swap_simulator::SwapSimulator, tenderly}, + simulator::{simulation_builder::SettlementSimulator, tenderly}, std::{collections::HashMap, num::NonZeroUsize, sync::Arc}, token_info::TokenInfoFetching, }; @@ -106,29 +106,30 @@ impl<'a> PriceEstimatorFactory<'a> { network.chain.id().to_string(), )) as Arc }); - let simulator = SwapSimulator::new( - balance_overrides.clone(), - network.settlement, + let settlement_contract = + GPv2Settlement::GPv2Settlement::new(network.settlement, web3.provider.clone()); + let simulator = SettlementSimulator::new( + settlement_contract, + Default::default(), + Default::default(), network.native_token, + balance_overrides.clone(), network.block_stream.clone(), - web3.clone(), - args.max_gas_per_tx, + tenderly.clone(), ) .await?; let verifier = TradeVerifier::new( - web3, - tenderly, simulator, + args.max_gas_per_tx, + tenderly, components.code_fetcher.clone(), balance_overrides, - network.settlement, args.quote_inaccuracy_limit.clone(), args.tokens_without_verification.iter().cloned().collect(), args.min_gas_amount_for_unverified_quotes, args.max_gas_amount_for_unverified_quotes, - ) - .await?; + ); Ok(Some(Arc::new(verifier))) } diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 1f6d0d0278..3060ed41c1 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -2,7 +2,6 @@ use { super::{Estimate, Verification}, crate::{ trade_finding::{ - Interaction, QuoteExecution, TradeKind, external::dto::{self, Side}, @@ -12,20 +11,20 @@ use { }, ::alloy::sol_types::SolCall, alloy::{ - primitives::{Address, U256, address, aliases::I512, map::AddressMap}, - rpc::types::{eth::state::StateOverride, state::AccountOverride}, + primitives::{Address, Bytes, U256, address, aliases::I512}, + rpc::types::state::AccountOverride, }, anyhow::{Context, Result}, balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, bigdecimal::BigDecimal, contracts::{ - GPv2Settlement, + WETH9, support::{AnyoneAuthenticator, Solver, Spardose, Trader}, }, - ethrpc::Web3, model::{ DomainSeparator, - order::{OrderData, OrderKind}, + interaction::InteractionData, + order::{BUY_ETH_ADDRESS, OrderData, OrderKind}, signature::{Signature, SigningScheme}, }, num::BigRational, @@ -39,8 +38,14 @@ use { nonzero::NonZeroU256, }, simulator::{ - encoding::{EncodedTrade, InteractionEncoding, encode_trade}, - swap_simulator::{EncodedSwap, SwapSimulator, TradeEncoding}, + encoding::{EncodedTrade, encode_trade}, + simulation_builder::{ + self as sim_builder, + ExecutionAmount, + PriceEncoding, + SettlementSimulator, + Solver as SimSolver, + }, tenderly::{self}, }, std::{ @@ -69,10 +74,10 @@ pub trait TradeVerifying: Send + Sync + 'static { #[derive(Clone)] pub struct TradeVerifier { tenderly: Option>, - simulator: SwapSimulator, + simulator: SettlementSimulator, + gas_limit: u64, code_fetcher: Arc, balance_overrides: Arc, - settlement: GPv2Settlement::Instance, quote_inaccuracy_limit: BigRational, tokens_without_verification: HashSet
, min_gas_amount_for_unverified_quotes: u32, @@ -84,36 +89,33 @@ impl TradeVerifier { const TRADER_IMPL: Address = address!("0000000000000000000000000000000000010000"); #[expect(clippy::too_many_arguments)] - pub async fn new( - web3: Web3, + pub fn new( + simulator: SettlementSimulator, + gas_limit: u64, tenderly: Option>, - simulator: SwapSimulator, code_fetcher: Arc, balance_overrides: Arc, - settlement: Address, quote_inaccuracy_limit: BigDecimal, tokens_without_verification: HashSet
, min_gas_amount_for_unverified_quotes: u32, max_gas_amount_for_unverified_quotes: u32, - ) -> Result { + ) -> Self { assert!( min_gas_amount_for_unverified_quotes <= max_gas_amount_for_unverified_quotes, "gas floor ({min_gas_amount_for_unverified_quotes}) exceeds gas ceiling \ ({max_gas_amount_for_unverified_quotes}) for unverified quotes" ); - let settlement_contract = - GPv2Settlement::GPv2Settlement::new(settlement, web3.provider.clone()); - Ok(Self { + Self { tenderly, simulator, + gas_limit, code_fetcher, balance_overrides, - settlement: settlement_contract, quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), tokens_without_verification, min_gas_amount_for_unverified_quotes, max_gas_amount_for_unverified_quotes, - }) + } } async fn verify_inner( @@ -155,92 +157,158 @@ impl TradeVerifier { query.in_amount.get(), ), }; - let simulator_query = simulator::swap_simulator::Query { - sell_token: query.sell_token, - buy_token: query.buy_token, - kind: query.kind, - sell_amount, - buy_amount, - receiver: verification.receiver, - sell_token_source: verification.sell_token_source, - buy_token_destination: verification.buy_token_destination, - from: verification.from, - tx_origin: trade.tx_origin(), - solver: solver_address, - tokens: tokens.clone(), - clearing_prices, - wrappers: Default::default(), + // Determine effective receiver (zero means owner receives) + let effective_receiver = if verification.receiver.is_zero() { + verification.from + } else { + verification.receiver }; - let mut swap = self - .simulator - .fake_swap(&simulator_query, TradeEncoding::Disadvantageous) - .await - .map_err(Error::SimulationFailed)?; + // storeBalance interactions bracket the settlement to measure the actual + // out_amount + let (tracked_token, tracked_owner) = match query.kind { + OrderKind::Sell => (query.buy_token, effective_receiver), + OrderKind::Buy => (query.sell_token, verification.from), + }; + let store_balance = InteractionData { + target: solver_address, + value: U256::ZERO, + call_data: Solver::Solver::storeBalanceCall { + token: tracked_token, + owner: tracked_owner, + countGas: true, + } + .abi_encode(), + }; - swap.overrides.extend(overrides); + // WETH unwrap so ETH buy orders can pay out native tokens + let weth_unwrap = (query.buy_token == BUY_ETH_ADDRESS).then(|| InteractionData { + target: self.simulator.native_token(), + value: U256::ZERO, + call_data: WETH9::WETH9::withdrawCall { wad: buy_amount }.abi_encode(), + }); - let mut pre_interactions = verification + // pre: [verification.pre, trade.pre, trade_setup, storeBalance_before] + let pre_interactions: Vec = map_interactions_data( + verification .pre_interactions .iter() - // pre_interactions introduced by the solver - .chain(trade.pre_interactions()) - .map(InteractionEncoding::encode) - .collect::>(); - - // Join custom pre_interactions in the following order: - // pre_interactions, trade setup interaction, encoded swap pre interactions - pre_interactions.extend([self - .trade_setup_interaction(out_amount, &verification, query, trade) - .encode()]); - pre_interactions.extend(swap.settlement.interactions.pre); - swap.settlement.interactions.pre = pre_interactions; - - // Join interactions introduced by the solver, set up in the following order: - // trade interactions, encoded swap interactions - let interactions = trade.interactions().map(InteractionEncoding::encode); - swap.settlement.interactions.main = interactions + .chain(trade.pre_interactions()), + ) + .into_iter() + .chain([self.trade_setup_interaction(out_amount, &verification, query, trade)]) + .chain([store_balance.clone()]) + .collect(); + + // main: [trade.main, weth_unwrap] + let main_interactions: Vec = map_interactions_data(trade.interactions()) .into_iter() - .chain(swap.settlement.interactions.main) + .chain(weth_unwrap) .collect(); - // Join post interactions in the following order: - // encoded swap post interactions, verification post interactions, - let post_interactions = verification - .post_interactions - .iter() - .map(InteractionEncoding::encode); - swap.settlement.interactions.post = swap - .settlement - .interactions - .post - .into_iter() - .chain(post_interactions) + // post: [storeBalance_after, verification.post] + let post_interactions: Vec = std::iter::once(store_balance) + .chain(map_interactions_data(verification.post_interactions.iter())) .collect(); - add_balance_queries(&mut swap, query, &verification); + let jit_trades = match trade { + TradeKind::Regular(t) => { + encode_jit_orders(&t.jit_orders, &tokens, &self.simulator.domain_separator())? + } + _ => vec![], + }; - if let TradeKind::Regular(trade) = trade { - swap.settlement.trades.extend(encode_jit_orders( - &trade.jit_orders, - &swap.settlement.tokens, - &self.simulator.domain_separator, - )?); - } - let block = *self.simulator.current_block.borrow(); - let output = self + // Set limit amounts to always pass the settlement check so the actual + // out_amount can be measured via the storeBalance interactions. + let (fake_sell_amount, fake_buy_amount) = match query.kind { + OrderKind::Sell => (sell_amount.get(), U256::ZERO), + OrderKind::Buy => (sell_amount.get().max(U256::from(u128::MAX)), buy_amount), + }; + let fake_order = OrderData { + sell_token: query.sell_token, + sell_amount: fake_sell_amount, + buy_token: query.buy_token, + buy_amount: fake_buy_amount, + receiver: Some(verification.receiver), + valid_to: u32::MAX, + app_data: Default::default(), + fee_amount: U256::ZERO, + kind: query.kind, + partially_fillable: false, + sell_token_balance: verification.sell_token_source, + buy_token_balance: verification.buy_token_destination, + }; + + let mut eth_call_inputs = self .simulator - .simulate_swap_with_solver(swap, block) - .await?; + .new_simulation_builder() + .add_order( + sim_builder::Order::new(fake_order) + .with_signature( + verification.from, + Signature::default_with(SigningScheme::Eip1271), + ) + .fill_at( + ExecutionAmount::Full, + PriceEncoding::Custom { + tokens: tokens.clone(), + clearing_prices, + }, + ), + ) + .from_solver(SimSolver::Real(solver_address)) + .with_pre_interactions(pre_interactions) + .with_main_interactions(main_interactions) + .with_post_interactions(post_interactions) + .add_extra_trades(jit_trades) + .build() + .await + .map_err(|e| Error::SimulationFailed(anyhow::anyhow!("{e}")))?; + + eth_call_inputs.state_overrides.extend(overrides); + + let settlement_target = eth_call_inputs + .request + .to + .as_ref() + .and_then(|t| t.to()) + .copied() + .expect("settlement target is always set"); + let calldata: Bytes = eth_call_inputs + .request + .input + .input + .clone() + .unwrap_or_default(); + + let solver_contract = Solver::Instance::new(solver_address, self.simulator.provider()); + let swap_call = solver_contract + .swap( + settlement_target, + tokens.clone(), + effective_receiver, + calldata, + ) + .from(solver_address) + .gas(self.gas_limit); + + let tx = swap_call.clone().into_transaction_request(); + let result = swap_call + .call() + .overrides(eth_call_inputs.state_overrides.clone()) + .block(eth_call_inputs.block.into()) + .await; if let Some(tenderly) = &self.tenderly - && let Err(err) = - tenderly.log_simulation_command(output.tx, output.overrides, block.number.into()) + && let Err(err) = tenderly.log_simulation_command( + tx, + eth_call_inputs.state_overrides, + eth_call_inputs.block.into(), + ) { tracing::debug!(?err, "could not log tenderly simulation command"); } - let output = output - .result + let output = result .context("failed to simulate quote") .map_err(Error::SimulationFailed); @@ -288,7 +356,7 @@ impl TradeVerifier { // It looks like the contract lost a lot of sell tokens but only because it was // the trader and had to pay for the trade. Adjust tokens lost downward. - if verification.from == *self.settlement.address() { + if verification.from == self.simulator.settlement_address() { summary .tokens_lost .entry(query.sell_token) @@ -297,7 +365,7 @@ impl TradeVerifier { // It looks like the contract gained a lot of buy tokens (negative loss) but // only because it was the receiver and got the payout. Adjust the tokens lost // upward. - if verification.receiver == *self.settlement.address() { + if verification.receiver == self.simulator.settlement_address() { summary .tokens_lost .entry(query.buy_token) @@ -371,8 +439,8 @@ impl TradeVerifier { verification: &mut Verification, query: &PriceQuery, trade: &TradeKind, - ) -> Result { - let mut overrides = AddressMap::default(); + ) -> Result { + let mut overrides = alloy::primitives::map::AddressMap::default(); // Provide mocked balances if possible to the spardose to allow it to // give some balances to the trader in order to verify trades even for @@ -480,16 +548,8 @@ impl TradeVerifier { .tx_origin() .is_some_and(|origin| origin != trade.solver()) { - let authenticator = self - .settlement - .authenticator() - .call() - .await - .context("could not fetch authenticator")?; - // TODO: when switching to the `SettlementSimulator` use - // `StateOverrideRequest::AuthenticateAddress` for this overrides.insert( - authenticator, + self.simulator.authenticator_address(), AccountOverride { code: Some(AnyoneAuthenticator::AnyoneAuthenticator::DEPLOYED_BYTECODE.clone()), ..Default::default() @@ -513,23 +573,23 @@ impl TradeVerifier { verification: &Verification, query: &PriceQuery, trade: &TradeKind, - ) -> Interaction { + ) -> InteractionData { let sell_amount = match query.kind { OrderKind::Sell => query.in_amount.get(), OrderKind::Buy => *out_amount, }; let setup_call = Solver::Solver::ensureTradePreconditionsCall { trader: verification.from, - settlementContract: *self.settlement.address(), + settlementContract: self.simulator.settlement_address(), sellToken: query.sell_token, sellAmount: sell_amount, spardose: Self::SPARDOSE, } .abi_encode(); - Interaction { + InteractionData { target: trade.solver(), value: U256::ZERO, - data: setup_call, + call_data: setup_call, } } } @@ -619,36 +679,6 @@ impl TradeVerifying for TradeVerifier { } } -fn add_balance_queries(swap: &mut EncodedSwap, query: &PriceQuery, verification: &Verification) { - let (token, owner) = match query.kind { - // track how much `buy_token` the `receiver` actually got - OrderKind::Sell => { - let receiver = match verification.receiver.is_zero() { - // Settlement contract sends fund to owner if receiver is the 0 address. - true => verification.from, - false => verification.receiver, - }; - - (query.buy_token, receiver) - } - // track how much `sell_token` the `from` address actually spent - OrderKind::Buy => (query.sell_token, verification.from), - }; - let query_balance_call = Solver::Solver::storeBalanceCall { - token, - owner, - countGas: true, - } - .abi_encode(); - - let interaction = (swap.solver, U256::ZERO, query_balance_call.into()); - - // query balance query at the end of pre-interactions - swap.settlement.interactions.pre.push(interaction.clone()); - // query balance right after we payed out all `buy_token` - swap.settlement.interactions.post.insert(0, interaction); -} - /// Analyzed output of `Solver::settle` smart contract call. #[derive(Debug)] struct SettleOutput { diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b69aa27e6c..97c5f4bf2e 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -76,6 +76,18 @@ impl SettlementSimulator { self.0.native_token } + pub fn provider(&self) -> DynProvider { + self.0.provider.clone() + } + + pub fn settlement_address(&self) -> Address { + *self.0.settlement.address() + } + + pub fn authenticator_address(&self) -> Address { + self.0.authenticator + } + pub fn new_simulation_builder(&self) -> SimulationBuilder { SimulationBuilder { simulator: self.clone(), @@ -84,7 +96,6 @@ impl SettlementSimulator { main_interactions: vec![], post_interactions: vec![], wrapper: WrapperConfig::NoWrapper, - prices: None, solver: None, auction_id: None, account_override_requests: vec![], @@ -115,7 +126,6 @@ pub struct SimulationBuilder { pub(crate) main_interactions: Vec, pub(crate) post_interactions: Vec, pub(crate) wrapper: WrapperConfig, - pub(crate) prices: Option, pub(crate) solver: Option, pub(crate) auction_id: Option, pub(crate) simulator: SettlementSimulator, @@ -152,11 +162,6 @@ impl SimulationBuilder { self } - pub fn with_prices(mut self, prices: Prices) -> Self { - self.prices = Some(prices); - self - } - pub fn from_solver(mut self, solver: Solver) -> Self { self.solver = Some(solver); self @@ -276,23 +281,6 @@ pub enum Solver { Fake(Option
), } -/// How clearing prices are determined for the encoded settlement. -pub enum Prices { - /// Derive clearing prices directly from the order's limit price. - /// - /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = - /// sell_amount`, exactly satisfying the order's limit with no surplus. - /// This should NOT be used when encoding solutions you actually want - /// to submit. - Limit, - // TODO: check how this can be made nicer. - /// Explicit token list and matching clearing prices. - Explicit { - tokens: Vec
, - clearing_prices: Vec, - }, -} - /// How much of an order should be filled during simulation. pub enum ExecutionAmount { /// Fill the full order amount (sell_amount for sell orders, buy_amount for @@ -306,17 +294,21 @@ pub enum ExecutionAmount { Explicit(U256), } -/// How the limit price of an order's trade entry should be encoded. +/// How clearing prices are determined for the encoded settlement. pub enum PriceEncoding { - /// Encode the exact sell_amount / buy_amount from the order as the limit - /// price. Default for production settlements. - Exact, - /// Set limit prices maximally permissive so the settlement always passes, - /// regardless of how many tokens the trader actually receives. Used for - /// quote verification so the actual out_amount can be measured afterward. - /// Sell orders: buy_amount = 0. Buy orders: sell_amount = max(sell_amount, - /// u128::MAX). - Disadvantageous, + /// Derive clearing prices directly from the order's limit price. + /// + /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = + /// sell_amount`, exactly satisfying the order's limit with no surplus. + LimitPrice, + /// Explicit token list and matching clearing prices. Use this when the + /// clearing prices differ from the order's limit — e.g. in trade + /// verification where the order amounts are set to always pass the limit + /// check and the solver's quoted prices are supplied separately. + Custom { + tokens: Vec
, + clearing_prices: Vec, + }, } /// A simulator-specific order that bundles the data needed to encode a trade. @@ -358,7 +350,7 @@ impl Order { pre_interactions: vec![], post_interactions: vec![], executed_amount: ExecutionAmount::Remaining, - price_encoding: PriceEncoding::Exact, + price_encoding: PriceEncoding::LimitPrice, } } @@ -509,8 +501,6 @@ pub enum BuildError { MissingBuyToken, #[error("could not override token balances to fund settlement contract")] FailedToOverrideBalances, - #[error("no strategy to compute the price vector was chosen")] - NoPriceEncoding, #[error("failed to query filled amount from settlement contract: {0}")] FilledAmountQuery(#[source] anyhow::Error), #[error("failed to parse app data: {0}")] diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 8652e8fac4..9211c641df 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -16,7 +16,6 @@ use { MergeConflict, Order, PriceEncoding, - Prices, SimulationBuilder, Solver, WrapperConfig, @@ -46,20 +45,18 @@ pub(crate) async fn encode( let executed_amount = executed_amount(&builder, order, block).await?; - let (tokens, clearing_prices) = match builder.prices { - Some(Prices::Explicit { - tokens, - clearing_prices, - }) => (tokens, clearing_prices), - // At limit price: price[sell_token] = buy_amount, price[buy_token] = sell_amount. - // This makes sell_amount * price[sell] / price[buy] = buy_amount exactly. - Some(Prices::Limit) => ( + // At limit price: price[sell_token] = buy_amount, price[buy_token] = + // sell_amount. This makes sell_amount * price[sell] / price[buy] = + // buy_amount exactly. + let (tokens, clearing_prices) = match &order.price_encoding { + PriceEncoding::LimitPrice => ( vec![order.data.sell_token, order.data.buy_token], vec![order.data.buy_amount, order.data.sell_amount], ), - None => { - return Err(BuildError::NoPriceEncoding); - } + PriceEncoding::Custom { + tokens, + clearing_prices, + } => (tokens.clone(), clearing_prices.clone()), }; let sell_token_index = tokens @@ -94,21 +91,8 @@ pub(crate) async fn encode( } } - let trade_data = match order.price_encoding { - PriceEncoding::Exact => std::borrow::Cow::Borrowed(&order.data), - PriceEncoding::Disadvantageous => { - let mut d = order.data.clone(); - match d.kind { - model::order::OrderKind::Sell => d.buy_amount = U256::ZERO, - model::order::OrderKind::Buy => { - d.sell_amount = d.sell_amount.max(U256::from(u128::MAX)) - } - } - std::borrow::Cow::Owned(d) - } - }; let trade = encode_trade( - &trade_data, + &order.data, &order.signature, order.owner, sell_token_index, From 474aaa3a63f303afe32c744c0d0dda0db0f8f3bc Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 14:02:13 +0100 Subject: [PATCH 090/154] Migrate to AccountOverrideRequest::BuyTokensForBuffers --- crates/orderbook/src/eip1271_simulation.rs | 3 ++- crates/simulator/tests/aave_replay.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index 00d60172cd..b8326501d8 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -5,6 +5,7 @@ use { shared::order_validation::{Eip1271Simulating, Eip1271SimulationError}, simulator::simulation_builder::{ self, + AccountOverrideRequest, Block, ExecutionAmount, Prices, @@ -45,7 +46,7 @@ impl Eip1271Simulating for OrderSimulatorAdapter { .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("parse app data")))? .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) - .fund_settlement_contract_with_buy_tokens() + .with_override(AccountOverrideRequest::BuyTokensForBuffers) .at_block(Block::Latest) .build() .await diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 547af4453e..ba41dc8f64 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -7,6 +7,7 @@ use { }, simulator::simulation_builder::{ self, + AccountOverrideRequest, Block, EthCallInputs, ExecutionAmount, @@ -216,7 +217,7 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI .expect("parameters_from_app_data should parse the app data") .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) - .fund_settlement_contract_with_buy_tokens() + .with_override(AccountOverrideRequest::BuyTokensForBuffers) .at_block(Block::Number(fork_block_mainnet)) .build() .await From 40e091d25ef9794ade22886507f55e5609c1b004 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 30 Apr 2026 15:08:39 +0100 Subject: [PATCH 091/154] fmt-toml: sort simulator Cargo.toml deps alphabetically --- crates/simulator/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/simulator/Cargo.toml b/crates/simulator/Cargo.toml index 1cb1960dfe..5c41b774e0 100644 --- a/crates/simulator/Cargo.toml +++ b/crates/simulator/Cargo.toml @@ -16,7 +16,6 @@ alloy-sol-types = { workspace = true } alloy-transport = { workspace = true } anyhow = { workspace = true } app-data = { workspace = true } -futures = { workspace = true } async-trait = { workspace = true } balance-overrides = { workspace = true } cached = { workspace = true } @@ -27,6 +26,7 @@ contracts = { workspace = true } derive_more = { workspace = true } eth-domain-types = { workspace = true } ethrpc = { workspace = true } +futures = { workspace = true } gas-price-estimation = { workspace = true } hex-literal = { workspace = true } http-client = { workspace = true } From ff58e63bb6662b983f559ef705116a601c14a6aa Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 14:09:10 +0000 Subject: [PATCH 092/154] Remove old code --- crates/orderbook/src/lib.rs | 1 - crates/orderbook/src/order_simulator.rs | 299 --------------------- crates/simulator/src/lib.rs | 1 - crates/simulator/src/swap_simulator.rs | 338 ------------------------ 4 files changed, 639 deletions(-) delete mode 100644 crates/orderbook/src/order_simulator.rs delete mode 100644 crates/simulator/src/swap_simulator.rs diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index c5c92d4a6a..2b380334fa 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -5,7 +5,6 @@ pub mod database; pub mod dto; mod ipfs; mod ipfs_app_data; -pub mod order_simulator; pub mod orderbook; mod quoter; pub mod run; diff --git a/crates/orderbook/src/order_simulator.rs b/crates/orderbook/src/order_simulator.rs deleted file mode 100644 index b6a604732b..0000000000 --- a/crates/orderbook/src/order_simulator.rs +++ /dev/null @@ -1,299 +0,0 @@ -use { - crate::dto::OrderSimulationResult, - alloy::{ - eips::BlockId, - primitives::{Address, Bytes, U256}, - rpc::types::state::AccountOverride, - }, - anyhow::{Context, Result, anyhow}, - app_data::WrapperCall, - balance_overrides::BalanceOverrideRequest, - contracts::support::{AnyoneAuthenticator, Trader}, - eth_domain_types::{BlockNo, NonZeroU256}, - model::order::Order, - shared::remaining_amounts, - simulator::{ - encoding::InteractionEncoding, - swap_simulator::{EncodedSwap, Query, SwapSimulator, TradeEncoding}, - tenderly, - }, - thiserror::Error, -}; -pub struct OrderSimulator { - simulator: SwapSimulator, - tenderly: Option>, - chain_id: String, -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("simulation could not be created")] - Other(anyhow::Error), - #[error("malformed input")] - MalformedInput(anyhow::Error), -} - -impl OrderSimulator { - pub fn new( - simulator: SwapSimulator, - chain_id: String, - tenderly: Option>, - ) -> Self { - Self { - simulator, - tenderly, - chain_id, - } - } - - /// Calculates the remaining sell and buy amounts - /// Returns a tuple of (remaining_sell, remaining_buy) - async fn remaining_amounts( - &self, - order: &Order, - block: Option, - ) -> Result<(U256, U256), Error> { - let mut filled_amount_call = self - .simulator - .settlement - .filledAmount(Bytes::from(order.metadata.uid.0)); - - if let Some(block) = block { - filled_amount_call = filled_amount_call.block(block); - } - let executed_amount = filled_amount_call - .call() - .await - .map_err(|err| Error::Other(anyhow!(err)))?; - - let remaining_order = remaining_amounts::Order { - kind: order.data.kind, - buy_amount: order.data.buy_amount, - sell_amount: order.data.sell_amount, - fee_amount: order.data.fee_amount, - executed_amount, - partially_fillable: order.data.partially_fillable, - }; - let remaining = remaining_amounts::Remaining::from_order(&remaining_order) - .with_context(|| { - format!( - "could not compute remaining amounts for order {}", - order.metadata.uid - ) - }) - .map_err(Error::Other)?; - let remaining_sell = remaining - .remaining(order.data.sell_amount) - .context("overflow computing remaining sell amount") - .map_err(Error::Other)?; - let remaining_buy = remaining - .remaining(order.data.buy_amount) - .context("overflow computing remaining buy amount") - .map_err(Error::Other)?; - - Ok((remaining_sell, remaining_buy)) - } - - /// Encodes an order for simulation. - /// - /// `executed_amount` overrides how much of the order has already been - /// filled (in the order's fill token: sell token for sell orders, buy - /// token for buy orders). When `None`, the executed amount is taken from - /// the order's metadata, which reflects the actual on-chain fill state. - pub async fn encode_order( - &self, - order: &Order, - wrappers: Vec, - block: Option, - ) -> Result { - let tokens = vec![order.data.sell_token, order.data.buy_token]; - // 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]; - let solver = Address::random(); - let (remaining_sell, remaining_buy) = - self.remaining_amounts(order, block.map(Into::into)).await?; - let query = Query { - sell_amount: NonZeroU256::try_from(remaining_sell).map_err(|err| { - Error::MalformedInput(anyhow!("sell_amount `{}`: {err}", remaining_sell)) - })?, - sell_token: order.data.sell_token, - buy_amount: remaining_buy, - buy_token: order.data.buy_token, - kind: order.data.kind, - receiver: order.data.receiver.unwrap_or(order.metadata.owner), - sell_token_source: order.data.sell_token_balance, - buy_token_destination: order.data.buy_token_balance, - from: order.metadata.owner, - tx_origin: None, - clearing_prices, - solver, - tokens, - wrappers: wrappers - .iter() - .map(|wrapper| simulator::encoding::WrapperCall { - address: wrapper.address, - data: wrapper.data.clone().into(), - }) - .collect(), - }; - - let swap = self - .simulator - .fake_swap(&query, TradeEncoding::Simple) - .await - .map_err(Error::Other)?; - let swap = add_interactions(swap, order); - let swap = self.add_state_overrides(&query, swap).await?; - - Ok(swap) - } - - /// Simulates a swap of the provided EncodedSwap. - /// - /// The result contains the transaction simulation error (if any) - /// and a full API request object that can be used to resimulate the swap - /// using Tenderly. - pub async fn simulate_swap( - &self, - swap: EncodedSwap, - block_number: Option, - ) -> Result { - let block_number = - block_number.unwrap_or_else(|| self.simulator.current_block.borrow().number); - let result = self - .simulator - .simulate_settle_call(swap, block_number) - .await - .map_err(Error::Other)?; - - let tenderly_request = simulator::tenderly::dto::Request { - transaction_index: None, - save: Some(true), - save_if_fails: Some(true), - ..simulator::tenderly::prepare_request( - self.chain_id.clone(), - &result.tx, - result.overrides, - BlockNo(block_number), - ) - .map_err(|err| Error::Other(anyhow!(err)))? - }; - - let tenderly_url = match &self.tenderly { - Some(api) => match api.simulate_and_share(tenderly_request.clone()).await { - Ok(url) => Some(url), - Err(err) => { - tracing::warn!(?err, "failed to create Tenderly simulation"); - None - } - }, - None => None, - }; - - Ok(OrderSimulationResult { - tenderly_request, - tenderly_url, - error: result.result.err().map(|err| err.to_string()), - }) - } - - pub async fn add_state_overrides( - &self, - query: &Query, - mut swap: EncodedSwap, - ) -> Result { - // Override authenticator with AnyoneAuthenticator so our fake solver is - // accepted. - let authenticator = self - .simulator - .settlement - .authenticator() - .call() - .await - .context("could not fetch authenticator") - .map_err(Error::Other)?; - swap.overrides.insert( - authenticator, - AccountOverride { - code: Some(AnyoneAuthenticator::AnyoneAuthenticator::DEPLOYED_BYTECODE.clone()), - ..Default::default() - }, - ); - - // Set up fake solver. - swap.overrides.insert( - query.solver, - AccountOverride { - // Allow solver simulations to proceed even if the real account holds no ETH. - // The number is obscenely large, but not max to avoid potential overflows. - // We had this set to eth(1), but some simulations require more than that on non-ETH - // networks e.g. polygon so it led to reverts. - balance: Some(U256::MAX / U256::from(2)), - ..Default::default() - }, - ); - - // Override trader address with Trader bytecode so EIP-1271 signature - // verification works for EOA traders (settlement calls isValidSignature - // on the trader address, which would revert for plain EOAs). - swap.overrides.insert( - query.from, - AccountOverride { - code: Some(Trader::Trader::DEPLOYED_BYTECODE.clone()), - ..Default::default() - }, - ); - - // Fund the settlement contract with enough buy tokens to be paid out. - // Add 1 to account for ceiling division in the settlement contract's - // executedBuyAmount calculation, which can be 1 unit above remaining_buy. - match self - .simulator - .balance_overrides - .state_override(BalanceOverrideRequest { - token: query.buy_token, - holder: *self.simulator.settlement.address(), - amount: query.buy_amount + U256::ONE, - }) - .await - { - Some((token, balance_override)) => { - swap.overrides.insert(token, balance_override); - } - None => { - tracing::warn!("Could not set state balance override for the settlement contract"); - } - }; - - Ok(swap) - } -} - -fn add_interactions(mut swap: EncodedSwap, order: &Order) -> EncodedSwap { - // Add order pre interactions before encoded swap's pre interactions - let pre_interactions = order - .interactions - .pre - .iter() - .map(InteractionEncoding::encode) - .collect(); - // Prepend order pre_interactions so they run first - let settlement_pre_interactions = - std::mem::replace(&mut swap.settlement.interactions.pre, pre_interactions); - swap.settlement - .interactions - .pre - .extend(settlement_pre_interactions); - - // Add order post interactions after encoded swap's post interactions - let post_interactions = order - .interactions - .post - .iter() - .map(InteractionEncoding::encode); - swap.settlement.interactions.post.extend(post_interactions); - - swap -} diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index 2725227850..ac90a39592 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -2,7 +2,6 @@ pub mod encoding; pub mod ethereum; pub mod simulation_builder; mod simulation_encoding; -pub mod swap_simulator; pub mod tenderly; mod utils; diff --git a/crates/simulator/src/swap_simulator.rs b/crates/simulator/src/swap_simulator.rs deleted file mode 100644 index bf0b9f840d..0000000000 --- a/crates/simulator/src/swap_simulator.rs +++ /dev/null @@ -1,338 +0,0 @@ -use { - crate::encoding::{ - EncodedSettlement, - EncodedTrade, - Interactions, - WrapperCall, - encode_trade, - encode_wrapper_settlement, - }, - alloy_primitives::{Address, Bytes, U256}, - alloy_provider::Provider, - alloy_rpc_types::{TransactionRequest, state::StateOverride}, - alloy_sol_types::SolCall, - anyhow::{Context, Result, anyhow}, - balance_overrides::BalanceOverriding, - contracts::{ - GPv2Settlement::{self}, - WETH9, - support::Solver::{self, Solver::swapReturn}, - }, - eth_domain_types::NonZeroU256, - ethrpc::{ - Web3, - block_stream::{BlockInfo, CurrentBlockWatcher}, - }, - model::{ - DomainSeparator, - order::{BUY_ETH_ADDRESS, BuyTokenDestination, OrderData, OrderKind, SellTokenSource}, - signature::{Signature, SigningScheme}, - }, - std::sync::Arc, -}; - -/// Query for the Swap Simulator to prepare a fake settlement with -/// Contains the minimum data required to encode a fake settlement -#[derive(Debug)] -pub struct Query { - pub sell_token: Address, - pub sell_amount: NonZeroU256, - pub buy_token: Address, - pub buy_amount: U256, - pub kind: OrderKind, - pub receiver: Address, - pub sell_token_source: SellTokenSource, - pub buy_token_destination: BuyTokenDestination, - pub from: Address, - pub tx_origin: Option
, - pub solver: Address, - pub tokens: Vec
, - pub clearing_prices: Vec, - pub wrappers: Vec, -} - -/// Controls how the trade is encoded for the provided Query -#[derive(Clone, Debug)] -pub enum TradeEncoding { - /// Encodes the trade amounts exactly as in the Query - Simple, - /// Encodes a trade with the most disadvantageous in and out amounts - /// possible (while taking possible overflows into account). Should the - /// trader not receive the amount promised by the [`Query`] the - /// simulation will still work and the actual out amount can be computed - /// afterwards. - Disadvantageous, -} - -#[derive(Clone)] -pub struct SwapSimulator { - pub balance_overrides: Arc, - pub settlement: GPv2Settlement::Instance, - pub native_token: Address, - pub domain_separator: DomainSeparator, - pub current_block: CurrentBlockWatcher, - pub web3: Web3, - pub gas_limit: u64, -} - -pub struct EncodedSwap { - pub settlement: EncodedSettlement, - pub overrides: StateOverride, - pub wrappers: Vec, - pub solver: Address, - pub receiver: Address, -} - -/// The output of a swap simulation. -/// -/// Contains the transaction request that was used to perform the simulation -/// (useful for introspection), The used state overrides and simulation result -/// The result is of generic type O, and depends on the type of simulation: -/// - solver swap simulation returns Solver::swapResult -/// - generic simulation returns Bytes -pub struct SwapSimulation { - pub tx: TransactionRequest, - pub overrides: StateOverride, - pub result: Result, -} - -impl SwapSimulator { - pub async fn new( - balance_overrides: Arc, - settlement: Address, - native_token: Address, - current_block: CurrentBlockWatcher, - web3: Web3, - gas_limit: u64, - ) -> Result { - let settlement = GPv2Settlement::GPv2Settlement::new(settlement, web3.provider.clone()); - let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); - - Ok(Self { - balance_overrides, - settlement, - native_token, - current_block, - web3, - gas_limit, - domain_separator, - }) - } - - /// Creates a fake swap based on the provided query - /// The result can be further post processed depending on the needs - /// - /// It can then be simulated with SwapSimulator::simulate_swap - /// - /// The trade_encoding controls if the trade should be encoded as-is, - /// based on the Query or if it should be encoded as the most - /// disadvantegous trade possible. - /// - /// The TradeEncoding::Disadvantegous is useful for price verification since - /// the resulting out amounts can be calculated later while allowing the - /// simulation to pass. - pub async fn fake_swap( - &self, - query: &Query, - trade_encoding: TradeEncoding, - ) -> Result { - let overrides = StateOverride::default(); - - let pre_interactions = Vec::new(); - let mut interactions = Vec::new(); - - if query.buy_token == BUY_ETH_ADDRESS { - // Because the `driver` manages `WETH` unwraps under the hood the `TradeFinder` - // does not have to emit unwraps to pay out `ETH` in a trade. - // However, for the simulation to be successful this has to happen so we do it - // ourselves here. - interactions.push(( - self.native_token, - U256::ZERO, - WETH9::WETH9::withdrawCall { - wad: query.buy_amount, - } - .abi_encode() - .into(), - )); - tracing::trace!("adding unwrap interaction for paying out ETH"); - } - - Ok(EncodedSwap { - settlement: EncodedSettlement { - tokens: query.tokens.to_vec(), - clearing_prices: query.clearing_prices.to_vec(), - trades: vec![encode_fake_trade(query, trade_encoding)?], - interactions: Interactions { - pre: pre_interactions, - main: interactions, - post: Vec::new(), - }, - }, - solver: query.tx_origin.unwrap_or(query.solver), - receiver: query.receiver, - overrides, - wrappers: query.wrappers.clone(), - }) - } - - /// For wrapped settlements, the Solver contract must call the first wrapper - /// (not the settlement directly). The wrapper then chains to the - /// settlement. For non-wrapped settlements, the Solver calls the - /// settlement contract directly. - fn get_target_and_calldata(&self, swap: &EncodedSwap) -> (Address, Bytes) { - if !swap.wrappers.is_empty() { - encode_wrapper_settlement(&swap.wrappers, swap.settlement.into_settle_call()) - .expect("wrappers is not empty") - } else { - ( - *self.settlement.address(), - swap.settlement.into_settle_call(), - ) - } - } - - /// Simulates a solver call to settlement contract with the provided swap - /// data. The swap call result is contained in the returned - /// SwapSimulation struct, along with the original TransactionRequest - /// and State overrides (if needed to be logged, or processed elsewhere). - /// - /// The caller supplies the `block` so the gas-price computation, the - /// pinned `.call()` block, and any downstream logging all reference the - /// same snapshot of chain state. - pub async fn simulate_swap_with_solver( - &self, - swap: EncodedSwap, - block: BlockInfo, - ) -> Result> { - let (settlement_target, calldata) = self.get_target_and_calldata(&swap); - let solver = Solver::Instance::new(swap.solver, self.web3.provider.clone()); - let overrides = swap.overrides; - - let swap = solver - .swap( - settlement_target, - swap.settlement.tokens.clone(), - swap.receiver, - calldata, - ) - .from(swap.solver) - .gas(self.gas_limit) - .gas_price( - u128::try_from(block.gas_price.saturating_mul(U256::from(2))) - .map_err(|err| anyhow!(err)) - .context("converting gas price to u128")?, - ); - - // Save the transaction request, so the caller can inspect it. - // For example, to create a tenderly API request and provide the ability to - // simulate it. - let tx = swap.clone().into_transaction_request(); - let result = swap - .call() - .overrides(overrides.clone()) - .block(block.number.into()) - .await - .map_err(|err| anyhow!(err)) - .context("failed to simulate swap"); - - Ok(SwapSimulation { - tx, - overrides, - result, - }) - } - - /// Simulate settle call on the latest block - pub async fn simulate_settle_call_on_latest( - &self, - swap: EncodedSwap, - ) -> Result> { - let block_number = self.current_block.borrow().number; - self.simulate_settle_call(swap, block_number).await - } - - pub async fn simulate_settle_call( - &self, - swap: EncodedSwap, - block_number: u64, - ) -> Result> { - let (settlement_target, calldata) = self.get_target_and_calldata(&swap); - - let overrides = swap.overrides; - let tx = TransactionRequest { - from: Some(swap.solver), - to: Some(settlement_target.into()), - input: calldata.into(), - gas: Some(self.gas_limit), - ..Default::default() - }; - - let result = self - .web3 - .provider - .call(tx.clone()) - .overrides(overrides.clone()) - .block(block_number.into()) - .await - .map_err(|err| anyhow!(err)); - - Ok(SwapSimulation { - tx, - overrides, - result, - }) - } -} - -fn encode_fake_trade(query: &Query, trade_encoding: TradeEncoding) -> Result { - let (sell_amount, buy_amount) = match trade_encoding { - TradeEncoding::Simple => (query.sell_amount.into(), query.buy_amount), - TradeEncoding::Disadvantageous => match query.kind { - OrderKind::Sell => (query.sell_amount.get(), U256::ZERO), - OrderKind::Buy => ( - query.sell_amount.get().max(U256::from(u128::MAX)), - query.buy_amount, - ), - }, - }; - - let fake_order = OrderData { - sell_token: query.sell_token, - sell_amount, - buy_token: query.buy_token, - buy_amount, - receiver: Some(query.receiver), - valid_to: u32::MAX, - app_data: Default::default(), - fee_amount: U256::ZERO, - kind: query.kind, - partially_fillable: false, - sell_token_balance: query.sell_token_source, - buy_token_balance: query.buy_token_destination, - }; - - let fake_signature = Signature::default_with(SigningScheme::Eip1271); - let encoded_trade = encode_trade( - &fake_order, - &fake_signature, - query.from, - // the tokens set length is small so the linear search is acceptable - query - .tokens - .iter() - .position(|token| token == &query.sell_token) - .context("missing sell token index")?, - query - .tokens - .iter() - .position(|token| token == &query.buy_token) - .context("missing buy token index")?, - match query.kind { - OrderKind::Sell => query.sell_amount.get(), - OrderKind::Buy => query.buy_amount, - }, - ); - - Ok(encoded_trade) -} From 104b759fc0c1b0ba1df3c915881750fcab4a51ec Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 14:45:11 +0000 Subject: [PATCH 093/154] assemble state overrides via simulator crate --- crates/e2e/tests/e2e/quote_verification.rs | 3 +- crates/price-estimation/src/factory.rs | 3 +- .../src/trade_verifier/mod.rs | 161 +++++++----------- crates/simulator/src/simulation_builder.rs | 2 +- crates/simulator/src/simulation_encoding.rs | 4 +- 5 files changed, 65 insertions(+), 108 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 6d1f5aa749..606f141bd9 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -199,7 +199,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { Default::default(), Default::default(), *onchain.contracts().weth.address(), - balance_overrides.clone(), + balance_overrides, block_stream.clone(), None, ) @@ -211,7 +211,6 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { gas_limit, None, Arc::new(web3.clone()), - balance_overrides, BigDecimal::zero(), Default::default(), 0, diff --git a/crates/price-estimation/src/factory.rs b/crates/price-estimation/src/factory.rs index cafa889f0d..4de8a55d71 100644 --- a/crates/price-estimation/src/factory.rs +++ b/crates/price-estimation/src/factory.rs @@ -113,7 +113,7 @@ impl<'a> PriceEstimatorFactory<'a> { Default::default(), Default::default(), network.native_token, - balance_overrides.clone(), + balance_overrides, network.block_stream.clone(), tenderly.clone(), ) @@ -124,7 +124,6 @@ impl<'a> PriceEstimatorFactory<'a> { args.max_gas_per_tx, tenderly, components.code_fetcher.clone(), - balance_overrides, args.quote_inaccuracy_limit.clone(), args.tokens_without_verification.iter().cloned().collect(), args.min_gas_amount_for_unverified_quotes, diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 3060ed41c1..5c6c5c14c6 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -10,16 +10,12 @@ use { trade_verifier::code_fetching::CodeFetching, }, ::alloy::sol_types::SolCall, - alloy::{ - primitives::{Address, Bytes, U256, address, aliases::I512}, - rpc::types::state::AccountOverride, - }, + alloy::primitives::{Address, Bytes, U256, address, aliases::I512}, anyhow::{Context, Result}, - balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, bigdecimal::BigDecimal, contracts::{ WETH9, - support::{AnyoneAuthenticator, Solver, Spardose, Trader}, + support::{Solver, Spardose, Trader}, }, model::{ DomainSeparator, @@ -41,6 +37,7 @@ use { encoding::{EncodedTrade, encode_trade}, simulation_builder::{ self as sim_builder, + AccountOverrideRequest, ExecutionAmount, PriceEncoding, SettlementSimulator, @@ -77,7 +74,6 @@ pub struct TradeVerifier { simulator: SettlementSimulator, gas_limit: u64, code_fetcher: Arc, - balance_overrides: Arc, quote_inaccuracy_limit: BigRational, tokens_without_verification: HashSet
, min_gas_amount_for_unverified_quotes: u32, @@ -94,7 +90,6 @@ impl TradeVerifier { gas_limit: u64, tenderly: Option>, code_fetcher: Arc, - balance_overrides: Arc, quote_inaccuracy_limit: BigDecimal, tokens_without_verification: HashSet
, min_gas_amount_for_unverified_quotes: u32, @@ -110,7 +105,6 @@ impl TradeVerifier { simulator, gas_limit, code_fetcher, - balance_overrides, quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), tokens_without_verification, min_gas_amount_for_unverified_quotes, @@ -126,8 +120,17 @@ impl TradeVerifier { out_amount: &U256, ) -> Result { let start = std::time::Instant::now(); - let overrides = self - .prepare_state_overrides(&mut verification, query, trade) + + if verification.from.is_zero() { + verification.from = Address::random(); + tracing::debug!( + trader = ?verification.from, + "use random trader address with fake balances" + ); + } + + let override_requests = self + .prepare_state_overrides(&verification, query, trade) .await?; // Use `tx_origin` if response indicates that a special address is needed for @@ -239,7 +242,7 @@ impl TradeVerifier { buy_token_balance: verification.buy_token_destination, }; - let mut eth_call_inputs = self + let mut builder = self .simulator .new_simulation_builder() .add_order( @@ -260,13 +263,17 @@ impl TradeVerifier { .with_pre_interactions(pre_interactions) .with_main_interactions(main_interactions) .with_post_interactions(post_interactions) - .add_extra_trades(jit_trades) + .add_extra_trades(jit_trades); + + for req in override_requests { + builder = builder.with_override(req); + } + + let eth_call_inputs = builder .build() .await .map_err(|e| Error::SimulationFailed(anyhow::anyhow!("{e}")))?; - eth_call_inputs.state_overrides.extend(overrides); - let settlement_target = eth_call_inputs .request .to @@ -436,12 +443,20 @@ impl TradeVerifier { /// trade. async fn prepare_state_overrides( &self, - verification: &mut Verification, + verification: &Verification, query: &PriceQuery, trade: &TradeKind, - ) -> Result { - let mut overrides = alloy::primitives::map::AddressMap::default(); + ) -> Result> { + let mut requests: Vec = Vec::new(); + // Setup the funding contract override. Regardless of whether or not the + // contract has funds, it needs to exist in order to not revert + // simulations (Solidity reverts on attempts to call addresses without + // any code). + requests.push(AccountOverrideRequest::Code { + account: Self::SPARDOSE, + code: Spardose::Spardose::DEPLOYED_BYTECODE.clone(), + }); // Provide mocked balances if possible to the spardose to allow it to // give some balances to the trader in order to verify trades even for // owners without balances. Note that we use a separate account for @@ -460,47 +475,17 @@ impl TradeVerifier { &query.kind, )?, }; - match self - .balance_overrides - .state_override(BalanceOverrideRequest { - token: query.sell_token, - holder: Self::SPARDOSE, - amount: spardose_amount_with_buffer(needed), - }) - .await - { - Some((token, solver_balance_override)) => { - tracing::trace!(?solver_balance_override, "solver balance override enabled"); - overrides.insert(token, solver_balance_override); - - if verification.from.is_zero() { - verification.from = Address::random(); - tracing::debug!( - trader = ?verification.from, - "use random trader address with fake balances" - ); - } - } - _ => { - tracing::warn!( - sell_token = ?query.sell_token, - "could not set spardose balance override for sell token; trade \ - verification will rely on the trader's real balance" - ); - if verification.from.is_zero() { - anyhow::bail!("trader is zero address and balances can not be faked"); - } - } - } + requests.push(AccountOverrideRequest::Balance { + holder: Self::SPARDOSE, + token: query.sell_token, + amount: spardose_amount_with_buffer(needed), + }); // Set up mocked trader. - overrides.insert( - verification.from, - AccountOverride { - code: Some(Trader::Trader::DEPLOYED_BYTECODE.clone()), - ..Default::default() - }, - ); + requests.push(AccountOverrideRequest::Code { + account: verification.from, + code: Trader::Trader::DEPLOYED_BYTECODE.clone(), + }); // If the trader is a smart contract we also need to store its implementation // to proxy into it during the simulation. @@ -510,56 +495,30 @@ impl TradeVerifier { .await .context("failed to fetch trader code")?; if !trader_impl.0.is_empty() { - overrides.insert( - Self::TRADER_IMPL, - AccountOverride { - code: Some(trader_impl), - ..Default::default() - }, - ); + requests.push(AccountOverrideRequest::Code { + account: Self::TRADER_IMPL, + code: trader_impl, + }); } - // Setup the funding contract override. Regardless of whether or not the - // contract has funds, it needs to exist in order to not revert - // simulations (Solidity reverts on attempts to call addresses without - // any code). - overrides.insert( - Self::SPARDOSE, - AccountOverride { - code: Some(Spardose::Spardose::DEPLOYED_BYTECODE.clone()), - ..Default::default() - }, - ); - - // Set up mocked solver. - let solver_override = AccountOverride { - code: Some(Solver::Solver::DEPLOYED_BYTECODE.clone()), - // Allow solver simulations to proceed even if the real account holds no ETH. - // The number is obscenely large, but not max to avoid potential overflows. - // We had this set to eth(1), but some simulations require more than that on non-ETH - // netowrks e.g. polygon so it led to reverts. - balance: Some(U256::MAX / U256::from(2)), - ..Default::default() - }; + // Set up mocked solver with enough ETH to proceed even if the real account + // holds none. + let solver = trade.tx_origin().unwrap_or(trade.solver()); + requests.push(AccountOverrideRequest::Code { + account: solver, + code: Solver::Solver::DEPLOYED_BYTECODE.clone(), + }); + requests.push(AccountOverrideRequest::SufficientEthBalance(solver)); - // If the trade requires a special tx.origin we also need to fake the - // authenticator. - if trade - .tx_origin() - .is_some_and(|origin| origin != trade.solver()) + // If the trade requires a special tx.origin we also need to allow list + // it in the authenticator + if let Some(custom_origin) = trade.tx_origin() + && custom_origin != trade.solver() { - overrides.insert( - self.simulator.authenticator_address(), - AccountOverride { - code: Some(AnyoneAuthenticator::AnyoneAuthenticator::DEPLOYED_BYTECODE.clone()), - ..Default::default() - }, - ); + requests.push(AccountOverrideRequest::AuthenticateAsSolver(custom_origin)) } - overrides.insert(trade.tx_origin().unwrap_or(trade.solver()), solver_override); - - Ok(overrides) + Ok(requests) } /// Create interaction that sets up the trade right before transfering diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 97c5f4bf2e..a82d18db4a 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -515,7 +515,7 @@ pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. SufficientEthBalance(Address), /// Allowlists an address as a solver to let it settle orders. - AuthenticateAddress(Address), + AuthenticateAsSolver(Address), /// Computes necessary state overrides for the requested balance. Balance { holder: Address, diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 9211c641df..5c5b3c725f 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -163,7 +163,7 @@ pub(crate) async fn encode( .push(AccountOverrideRequest::SufficientEthBalance(addr)); builder .account_override_requests - .push(AccountOverrideRequest::AuthenticateAddress(addr)); + .push(AccountOverrideRequest::AuthenticateAsSolver(addr)); addr } None => return Err(BuildError::NoSolver), @@ -234,7 +234,7 @@ async fn build_final_state_overrides( addr, AccountOverride::default().with_balance(U256::MAX / U256::from(2)), )), - AccountOverrideRequest::AuthenticateAddress(addr) => { + AccountOverrideRequest::AuthenticateAsSolver(addr) => { // GPv2AllowListAuthentication stores `mapping(address => bool) managers` // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ // slot_padded). From 12dead3b6525ed73debf0091f172dfdcc83d0a10 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 14:55:20 +0000 Subject: [PATCH 094/154] best effort override resolution --- crates/simulator/src/simulation_builder.rs | 7 +- crates/simulator/src/simulation_encoding.rs | 116 ++++++++++---------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index a82d18db4a..ddec6d0838 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -499,18 +499,15 @@ pub enum BuildError { MissingSellToken, #[error("buy token not found in token list")] MissingBuyToken, - #[error("could not override token balances to fund settlement contract")] - FailedToOverrideBalances, #[error("failed to query filled amount from settlement contract: {0}")] FilledAmountQuery(#[source] anyhow::Error), #[error("failed to parse app data: {0}")] AppDataParse(#[source] serde_json::Error), #[error("both wrappers and flashloans cannot be encoded in the same settlement")] FlashloanWrappersIncompatible, - #[error("conflicting state overrides for the same account: {0}")] - ConflictingStateOverrides(#[source] MergeConflict), } +#[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. SufficientEthBalance(Address), @@ -538,7 +535,7 @@ pub enum AccountOverrideRequest { /// Error returned when two [`AccountOverride`]s set the same field for the same /// address and cannot be merged. #[derive(Debug, thiserror::Error)] -pub enum MergeConflict { +pub(crate) enum MergeConflict { #[error("both overrides set the ETH balance")] Balance, #[error("both overrides set the nonce")] diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 5c5b3c725f..82c2a983fd 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -29,7 +29,6 @@ use { alloy_sol_types::SolCall, balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, model::order::OrderKind, - std::sync::Arc, }; pub(crate) async fn encode( @@ -170,10 +169,10 @@ pub(crate) async fn encode( }; let state_overrides = build_final_state_overrides( builder.account_override_requests, - Arc::clone(&builder.simulator.0.balance_overrides), + builder.simulator.0.balance_overrides.as_ref(), builder.simulator.0.authenticator, ) - .await?; + .await; Ok(EthCallInputs { request: TransactionRequest { @@ -219,72 +218,79 @@ async fn executed_amount( }) } -/// Resolves all [`AccountOverrideRequest`]s concurrently, merges them -/// and returns the final [`StateOverride`]. +/// Resolves all [`AccountOverrideRequest`]s concurrently on a best-effort +/// basis. Failures are logged and the corresponding override is skipped rather +/// than aborting the whole build. async fn build_final_state_overrides( requests: Vec, - balance_overrides: Arc, + balance_overrides: &dyn BalanceOverriding, authenticator: Address, -) -> Result { - let futures = requests.into_iter().map(|request| { - let balance_overrides = Arc::clone(&balance_overrides); - async move { - match request { - AccountOverrideRequest::SufficientEthBalance(addr) => Ok(( - addr, - AccountOverride::default().with_balance(U256::MAX / U256::from(2)), - )), - AccountOverrideRequest::AuthenticateAsSolver(addr) => { - // GPv2AllowListAuthentication stores `mapping(address => bool) managers` - // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ - // slot_padded). - let mut buf = [0u8; 64]; - buf[12..32].copy_from_slice(addr.as_slice()); - buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); - let slot = keccak256(buf); - Ok(( - authenticator, - AccountOverride::default() - .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), - )) - } - AccountOverrideRequest::Balance { - holder, - token, - amount, - } => balance_overrides +) -> StateOverride { + let futures = requests.into_iter().map(|request| async move { + match request { + AccountOverrideRequest::SufficientEthBalance(addr) => Some(( + addr, + AccountOverride::default().with_balance(U256::MAX / U256::from(2)), + )), + AccountOverrideRequest::AuthenticateAsSolver(addr) => { + // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ + // slot_padded). + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(addr.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + let slot = keccak256(buf); + Some(( + authenticator, + AccountOverride::default() + .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), + )) + } + AccountOverrideRequest::Balance { + holder, + token, + amount, + } => { + let result = balance_overrides .state_override(BalanceOverrideRequest { token, holder, amount, }) - .await - .ok_or(BuildError::FailedToOverrideBalances), - AccountOverrideRequest::BuyTokensForBuffers => { - unreachable!( - "replaced with specific Balance requests before state overrides get \ - computed" - ) + .await; + if result.is_none() { + tracing::warn!(%token, %holder, "failed to compute balance state override, skipping"); } - AccountOverrideRequest::Code { account, code } => Ok(( - account, - AccountOverride { - code: Some(code), - ..Default::default() - }, - )), - AccountOverrideRequest::Custom { account, state } => Ok((account, state)), + result } + AccountOverrideRequest::BuyTokensForBuffers => { + unreachable!( + "replaced with specific Balance requests before state overrides get \ + computed" + ) + } + AccountOverrideRequest::Code { account, code } => Some(( + account, + AccountOverride { + code: Some(code), + ..Default::default() + }, + )), + AccountOverrideRequest::Custom { account, state } => Some((account, state)), } }); - let resolved_overrides = futures::future::try_join_all(futures).await?; let mut state_overrides = StateOverride::default(); - for (address, account_override) in resolved_overrides { - apply_account_override(&mut state_overrides, address, account_override) - .map_err(BuildError::ConflictingStateOverrides)?; + for (address, account_override) in futures::future::join_all(futures) + .await + .into_iter() + .flatten() + { + if let Err(err) = apply_account_override(&mut state_overrides, address, account_override) { + tracing::warn!(?err, %address, "conflicting state overrides for address, skipping"); + } } - Ok(state_overrides) + state_overrides } /// Merges `new` into `existing` field by field. @@ -350,7 +356,7 @@ fn merge_account_override( /// /// If `address` already has an entry, the overrides are merged via /// [`merge_account_override`]. Returns an error on conflict. -pub fn apply_account_override( +pub(crate) fn apply_account_override( overrides: &mut StateOverride, address: Address, new: AccountOverride, From aad3997db1603dfc2526211c59b6e94c11ac3827 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 15:12:01 +0000 Subject: [PATCH 095/154] Remove customize functionality --- crates/simulator/src/simulation_builder.rs | 12 +----------- crates/simulator/src/simulation_encoding.rs | 5 +---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index ddec6d0838..5eef88a7ec 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -252,19 +252,9 @@ impl SimulationBuilder { /// Finishes the simulation struct based on the configuration thus far. pub async fn build(self) -> Result { - self.build_with_modifications(|_| {}).await - } - - /// Same as `build()` but allows the caller to alter the simulation - /// before it gets finalized. This should only be used for very specific - /// setups. - pub async fn build_with_modifications( - self, - customize: impl FnOnce(&mut EncodedSettlement), - ) -> Result { // Forward to a helper function to split the boring repetitive builder // code from the non-trivial code that actually does the encoding. - crate::simulation_encoding::encode(self, customize).await + crate::simulation_encoding::encode(self).await } } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 82c2a983fd..24b53442b4 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -31,10 +31,7 @@ use { model::order::OrderKind, }; -pub(crate) async fn encode( - mut builder: SimulationBuilder, - customize: impl FnOnce(&mut EncodedSettlement), -) -> Result { +pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result { let order = builder.order.as_ref().ok_or(BuildError::NoOrder)?; let block = match builder.block { From 56b020bfed03189a8f1ff21dff0006290c7040b1 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 15:20:10 +0000 Subject: [PATCH 096/154] Support multiple orders --- crates/orderbook/src/orderbook.rs | 54 ++++---- .../src/trade_verifier/mod.rs | 126 +++++++---------- crates/simulator/src/simulation_builder.rs | 24 +--- crates/simulator/src/simulation_encoding.rs | 129 ++++++++++-------- 4 files changed, 158 insertions(+), 175 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index afb38f4bbd..ed29cdb970 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -639,14 +639,12 @@ impl Orderbook { let sim = order_simulator .new_simulation_builder() - .add_order( - simulation_builder::Order::new(order.data) - .with_signature(order.metadata.owner, order.signature) - .fill_at( - simulation_builder::ExecutionAmount::Remaining, - simulation_builder::PriceEncoding::LimitPrice, - ), - ) + .add_orders([simulation_builder::Order::new(order.data) + .with_signature(order.metadata.owner, order.signature) + .fill_at( + simulation_builder::ExecutionAmount::Remaining, + simulation_builder::PriceEncoding::LimitPrice, + )]) .parameters_from_app_data(&full_app_data) .context("failed to parse app data")? .at_block( @@ -685,27 +683,25 @@ impl Orderbook { let sim = order_simulator .new_simulation_builder() - .add_order( - simulation_builder::Order::new(OrderData { - sell_token: request.sell_token, - buy_token: request.buy_token, - sell_amount: request.sell_amount.into(), - buy_amount: request.buy_amount, - kind: request.kind, - receiver: request.receiver, - sell_token_balance: request.sell_token_balance, - buy_token_balance: request.buy_token_balance, - fee_amount: request.fee_amount, - valid_to: request.valid_to, - app_data: AppDataHash(app_data_hash.into()), - partially_fillable: request.partially_fillable, - }) - .with_signature(request.owner, request.signature) - .fill_at( - simulation_builder::ExecutionAmount::Full, - simulation_builder::PriceEncoding::LimitPrice, - ), - ) + .add_orders([simulation_builder::Order::new(OrderData { + sell_token: request.sell_token, + buy_token: request.buy_token, + sell_amount: request.sell_amount.into(), + buy_amount: request.buy_amount, + kind: request.kind, + receiver: request.receiver, + sell_token_balance: request.sell_token_balance, + buy_token_balance: request.buy_token_balance, + fee_amount: request.fee_amount, + valid_to: request.valid_to, + app_data: AppDataHash(app_data_hash.into()), + partially_fillable: request.partially_fillable, + }) + .with_signature(request.owner, request.signature) + .fill_at( + simulation_builder::ExecutionAmount::Full, + simulation_builder::PriceEncoding::LimitPrice, + )]) .parameters_from_app_data(&request.app_data) .context("failed to parse app data")? .at_block( diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 5c6c5c14c6..b6a14e9e21 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -34,7 +34,6 @@ use { nonzero::NonZeroU256, }, simulator::{ - encoding::{EncodedTrade, encode_trade}, simulation_builder::{ self as sim_builder, AccountOverrideRequest, @@ -43,7 +42,7 @@ use { SettlementSimulator, Solver as SimSolver, }, - tenderly::{self}, + tenderly, }, std::{ collections::{HashMap, HashSet}, @@ -214,20 +213,13 @@ impl TradeVerifier { .chain(map_interactions_data(verification.post_interactions.iter())) .collect(); - let jit_trades = match trade { - TradeKind::Regular(t) => { - encode_jit_orders(&t.jit_orders, &tokens, &self.simulator.domain_separator())? - } - _ => vec![], - }; - // Set limit amounts to always pass the settlement check so the actual // out_amount can be measured via the storeBalance interactions. let (fake_sell_amount, fake_buy_amount) = match query.kind { OrderKind::Sell => (sell_amount.get(), U256::ZERO), OrderKind::Buy => (sell_amount.get().max(U256::from(u128::MAX)), buy_amount), }; - let fake_order = OrderData { + let fake_order = sim_builder::Order::new(OrderData { sell_token: query.sell_token, sell_amount: fake_sell_amount, buy_token: query.buy_token, @@ -240,30 +232,65 @@ impl TradeVerifier { partially_fillable: false, sell_token_balance: verification.sell_token_source, buy_token_balance: verification.buy_token_destination, + }) + .with_signature( + verification.from, + Signature::default_with(SigningScheme::Eip1271), + ) + .fill_at( + ExecutionAmount::Full, + PriceEncoding::Custom { + tokens: tokens.clone(), + clearing_prices, + }, + ); + + let jit_orders: Vec = match trade { + TradeKind::Regular(t) => t + .jit_orders + .iter() + .map(|jit_order| { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: U256::ZERO, + kind: match &jit_order.side { + Side::Buy => OrderKind::Buy, + Side::Sell => OrderKind::Sell, + }, + partially_fillable: jit_order.partially_fillable, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = recover_jit_order_owner( + jit_order, + &order_data, + &self.simulator.domain_separator(), + )?; + Ok(sim_builder::Order::new(order_data) + .with_signature(owner, signature) + .fill_at( + ExecutionAmount::Explicit(jit_order.executed_amount), + PriceEncoding::LimitPrice, + )) + }) + .collect::>()?, + _ => vec![], }; let mut builder = self .simulator .new_simulation_builder() - .add_order( - sim_builder::Order::new(fake_order) - .with_signature( - verification.from, - Signature::default_with(SigningScheme::Eip1271), - ) - .fill_at( - ExecutionAmount::Full, - PriceEncoding::Custom { - tokens: tokens.clone(), - clearing_prices, - }, - ), - ) + .add_orders(std::iter::once(fake_order).chain(jit_orders)) .from_solver(SimSolver::Real(solver_address)) .with_pre_interactions(pre_interactions) .with_main_interactions(main_interactions) - .with_post_interactions(post_interactions) - .add_extra_trades(jit_trades); + .with_post_interactions(post_interactions); for req in override_requests { builder = builder.with_override(req); @@ -754,53 +781,6 @@ pub struct PriceQuery { pub in_amount: NonZeroU256, } -pub fn encode_jit_orders( - jit_orders: &[dto::JitOrder], - tokens: &[Address], - domain_separator: &DomainSeparator, -) -> Result> { - jit_orders - .iter() - .map(|jit_order| { - let order_data = OrderData { - sell_token: jit_order.sell_token, - buy_token: jit_order.buy_token, - receiver: Some(jit_order.receiver), - sell_amount: jit_order.sell_amount, - buy_amount: jit_order.buy_amount, - valid_to: jit_order.valid_to, - app_data: jit_order.app_data, - fee_amount: U256::ZERO, - kind: match &jit_order.side { - Side::Buy => OrderKind::Buy, - Side::Sell => OrderKind::Sell, - }, - partially_fillable: jit_order.partially_fillable, - sell_token_balance: jit_order.sell_token_source, - buy_token_balance: jit_order.buy_token_destination, - }; - let (owner, signature) = - recover_jit_order_owner(jit_order, &order_data, domain_separator)?; - - Ok(encode_trade( - &order_data, - &signature, - owner, - // the tokens set length is small so the linear search is acceptable - tokens - .iter() - .position(|token| *token == jit_order.sell_token) - .context("missing jit order sell token index")?, - tokens - .iter() - .position(|token| *token == jit_order.buy_token) - .context("missing jit order buy token index")?, - jit_order.executed_amount, - )) - }) - .collect::>>() -} - /// Recovers the owner and signature from a `JitOrder`. fn recover_jit_order_owner( jit_order: &dto::JitOrder, diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 5eef88a7ec..f94f481e16 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,8 +1,5 @@ use { - crate::{ - encoding::{EncodedSettlement, EncodedTrade, WrapperCall}, - tenderly::dto::StateObject, - }, + crate::{encoding::WrapperCall, tenderly::dto::StateObject}, alloy_primitives::{Address, B256, Bytes, TxKind, U256}, alloy_provider::{DynProvider, Provider}, alloy_rpc_types::{ @@ -91,7 +88,7 @@ impl SettlementSimulator { pub fn new_simulation_builder(&self) -> SimulationBuilder { SimulationBuilder { simulator: self.clone(), - order: None, + orders: vec![], pre_interactions: vec![], main_interactions: vec![], post_interactions: vec![], @@ -99,7 +96,6 @@ impl SettlementSimulator { solver: None, auction_id: None, account_override_requests: vec![], - extra_trades: vec![], block: Block::Latest, } } @@ -121,7 +117,7 @@ pub enum Block { /// /// Call [`SimulationBuilder::build`] when done to produce a [`SettlementCall`]. pub struct SimulationBuilder { - pub(crate) order: Option, + pub(crate) orders: Vec, pub(crate) pre_interactions: Vec, pub(crate) main_interactions: Vec, pub(crate) post_interactions: Vec, @@ -130,15 +126,12 @@ pub struct SimulationBuilder { pub(crate) auction_id: Option, pub(crate) simulator: SettlementSimulator, pub(crate) account_override_requests: Vec, - pub(crate) extra_trades: Vec, pub(crate) block: Block, } impl SimulationBuilder { - // TODO: support multiple orders to support use case of encoding solutions - // in the driver and the trade verification (requires JIT orders) - pub fn add_order(mut self, order: Order) -> Self { - self.order = Some(order); + pub fn add_orders(mut self, orders: impl IntoIterator) -> Self { + self.orders.extend(orders); self } @@ -243,13 +236,6 @@ impl SimulationBuilder { self } - /// Appends pre-encoded trades (e.g. JIT orders) to the settlement. - /// These are appended after the primary order's trade entry. - pub fn add_extra_trades(mut self, trades: Vec) -> Self { - self.extra_trades.extend(trades); - self - } - /// Finishes the simulation struct based on the configuration thus far. pub async fn build(self) -> Result { // Forward to a helper function to split the boring repetitive builder diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 24b53442b4..f6cee66c51 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -28,26 +28,38 @@ use { }, alloy_sol_types::SolCall, balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, - model::order::OrderKind, + model::{interaction::InteractionData, order::OrderKind}, }; pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result { - let order = builder.order.as_ref().ok_or(BuildError::NoOrder)?; + if builder.orders.is_empty() { + return Err(BuildError::NoOrder); + } let block = match builder.block { Block::Latest => builder.simulator.0.current_block.borrow().number, Block::Number(n) => n, }; - let executed_amount = executed_amount(&builder, order, block).await?; + let executed_amounts = futures::future::try_join_all( + builder + .orders + .iter() + .map(|o| executed_amount(&builder, o, block)), + ) + .await?; + // The first order determines the settlement-level token list and clearing + // prices. Subsequent orders (e.g. JIT orders) must have their tokens + // present in that list. // At limit price: price[sell_token] = buy_amount, price[buy_token] = // sell_amount. This makes sell_amount * price[sell] / price[buy] = // buy_amount exactly. - let (tokens, clearing_prices) = match &order.price_encoding { + let first = &builder.orders[0]; + let (tokens, clearing_prices) = match &first.price_encoding { PriceEncoding::LimitPrice => ( - vec![order.data.sell_token, order.data.buy_token], - vec![order.data.buy_amount, order.data.sell_amount], + vec![first.data.sell_token, first.data.buy_token], + vec![first.data.buy_amount, first.data.sell_amount], ), PriceEncoding::Custom { tokens, @@ -55,68 +67,77 @@ pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result (tokens.clone(), clearing_prices.clone()), }; - let sell_token_index = tokens - .iter() - .position(|t| *t == order.data.sell_token) - .ok_or(BuildError::MissingSellToken)?; - let buy_token_index = tokens - .iter() - .position(|t| *t == order.data.buy_token) - .ok_or(BuildError::MissingBuyToken)?; + // Replace BuyTokensForBuffers placeholders using the first order's data. + // Must happen before clearing_prices is moved into EncodedSettlement. + { + let first_exec = executed_amounts[0]; + let sell_idx = tokens + .iter() + .position(|t| *t == first.data.sell_token) + .ok_or(BuildError::MissingSellToken)?; + let buy_idx = tokens + .iter() + .position(|t| *t == first.data.buy_token) + .ok_or(BuildError::MissingBuyToken)?; + for request in &mut builder.account_override_requests { + if matches!(request, AccountOverrideRequest::BuyTokensForBuffers) { + let amount = match first.data.kind { + OrderKind::Sell => clearing_prices[sell_idx] + .saturating_mul(first_exec) + .checked_div(clearing_prices[buy_idx]) + .unwrap_or(U256::MAX), + OrderKind::Buy => first_exec, + } + // give 1 wei extra to avoid issues with rounding divisions + .saturating_add(U256::ONE); - // Replace BuyTokensForBuffers placeholders with concrete Balance requests - // now that the required amounts are known. Must happen before clearing_prices - // is moved into EncodedSettlement. - for request in &mut builder.account_override_requests { - if matches!(request, AccountOverrideRequest::BuyTokensForBuffers) { - let amount = match order.data.kind { - OrderKind::Sell => clearing_prices[sell_token_index] - .saturating_mul(executed_amount) - .checked_div(clearing_prices[buy_token_index]) - .unwrap_or(U256::MAX), - OrderKind::Buy => executed_amount, + *request = AccountOverrideRequest::Balance { + holder: *builder.simulator.0.settlement.address(), + token: first.data.buy_token, + amount, + }; } - // give 1 wei extra to avoid issues with rounding divisions - .saturating_add(U256::ONE); - - *request = AccountOverrideRequest::Balance { - holder: *builder.simulator.0.settlement.address(), - token: order.data.buy_token, - amount, - }; } } - let trade = encode_trade( - &order.data, - &order.signature, - order.owner, - sell_token_index, - buy_token_index, - executed_amount, - ); - - let order_pre = &order.pre_interactions; - let order_post = &order.post_interactions; - - let mut trades = vec![trade]; - trades.extend(builder.extra_trades); + // Encode every order as a trade, then collect all their interactions. + let mut trades = Vec::with_capacity(builder.orders.len()); + let mut all_order_pre: Vec = vec![]; + let mut all_order_post: Vec = vec![]; + for (order, exec) in builder.orders.iter().zip(&executed_amounts) { + let sell_idx = tokens + .iter() + .position(|t| *t == order.data.sell_token) + .ok_or(BuildError::MissingSellToken)?; + let buy_idx = tokens + .iter() + .position(|t| *t == order.data.buy_token) + .ok_or(BuildError::MissingBuyToken)?; + trades.push(encode_trade( + &order.data, + &order.signature, + order.owner, + sell_idx, + buy_idx, + *exec, + )); + all_order_pre.extend_from_slice(&order.pre_interactions); + all_order_post.extend_from_slice(&order.post_interactions); + } - let mut settlement = EncodedSettlement { + let settlement = EncodedSettlement { tokens, clearing_prices, trades, interactions: Interactions { - // order's pre-hooks run before any additional pre-interactions - pre: encode_interactions(order_pre.iter().chain(&builder.pre_interactions)), + // order pre-hooks run before any additional pre-interactions + pre: encode_interactions(all_order_pre.iter().chain(&builder.pre_interactions)), main: encode_interactions(&builder.main_interactions), - // additional post-interactions run before the order's post-hooks - post: encode_interactions(builder.post_interactions.iter().chain(order_post)), + // additional post-interactions run before order post-hooks + post: encode_interactions(builder.post_interactions.iter().chain(&all_order_post)), }, }; - customize(&mut settlement); - let settle_calldata = { let mut bytes = settlement.into_settle_call().to_vec(); if let Some(id) = builder.auction_id { From 4dd3628ba203e33f2dc003498145f587ea40fc8b Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 15:31:03 +0000 Subject: [PATCH 097/154] nicer API --- crates/orderbook/src/orderbook.rs | 4 ++-- .../src/trade_verifier/mod.rs | 15 +++++--------- crates/simulator/src/simulation_builder.rs | 20 ++++++++++--------- crates/simulator/src/simulation_encoding.rs | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index ed29cdb970..16e6a1802a 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -652,7 +652,7 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) + .with_overrides([simulation_builder::AccountOverrideRequest::BuyTokensForBuffers]) .from_solver(simulation_builder::Solver::Fake(None)) .build() .await @@ -710,7 +710,7 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_override(simulation_builder::AccountOverrideRequest::BuyTokensForBuffers) + .with_overrides([simulation_builder::AccountOverrideRequest::BuyTokensForBuffers]) .from_solver(simulation_builder::Solver::Fake(None)) .build() .await diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index b6a14e9e21..82e7632e7d 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -40,7 +40,7 @@ use { ExecutionAmount, PriceEncoding, SettlementSimulator, - Solver as SimSolver, + Solver as SimulationSolver, }, tenderly, }, @@ -283,20 +283,15 @@ impl TradeVerifier { _ => vec![], }; - let mut builder = self + let eth_call_inputs = self .simulator .new_simulation_builder() .add_orders(std::iter::once(fake_order).chain(jit_orders)) - .from_solver(SimSolver::Real(solver_address)) + .from_solver(SimulationSolver::OriginUnaltered(solver_address)) .with_pre_interactions(pre_interactions) .with_main_interactions(main_interactions) - .with_post_interactions(post_interactions); - - for req in override_requests { - builder = builder.with_override(req); - } - - let eth_call_inputs = builder + .with_post_interactions(post_interactions) + .with_overrides(override_requests) .build() .await .map_err(|e| Error::SimulationFailed(anyhow::anyhow!("{e}")))?; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f94f481e16..475f74c9b7 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -227,12 +227,13 @@ impl SimulationBuilder { Ok(self) } - /// Queues an [`AccountOverrideRequest`] to be resolved and applied during - /// [`build`](Self::build). Multiple requests may target the same address; - /// non-conflicting fields are merged and conflicts produce - /// [`BuildError::ConflictingStateOverrides`]. - pub fn with_override(mut self, request: AccountOverrideRequest) -> Self { - self.account_override_requests.push(request); + /// Queues [`AccountOverrideRequest`]s to be resolved and applied during + /// [`build`](Self::build). Multiple requests may target the same address. + pub fn with_overrides( + mut self, + requests: impl IntoIterator, + ) -> Self { + self.account_override_requests.extend(requests); self } @@ -248,9 +249,10 @@ pub enum Solver { /// Simulation assumes this is an actual solver so no state overrides will /// be applied to allow list it explicitly. /// If you need a very specific solver setup for your simulation consider - /// using this and explicitly add the necessary state overrides yourself - /// with `Simulation::build_with_modifications()`. - Real(Address), + /// using this and explicitly adding the necessary + /// [`AccountOverrideRequest`]s using with + /// [`SimulationBuilder::with_overrides()`]. + OriginUnaltered(Address), /// A fake solver for simulation. Uses the provided address or generates a /// random one. The simulation builder will automatically set the required /// state overrides to give it enough ETH and allow list it as a solver. diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index f6cee66c51..1eb78cd76d 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -172,7 +172,7 @@ pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result addr, + Some(Solver::OriginUnaltered(addr)) => addr, Some(Solver::Fake(opt)) => { let addr = opt.unwrap_or_else(Address::random); builder From a0c3bccd108d79f40fffd5b02f13e6164ecd35f1 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 15:34:23 +0000 Subject: [PATCH 098/154] nits --- crates/simulator/src/simulation_builder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 475f74c9b7..a568acfe56 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -423,7 +423,6 @@ impl EthCallInputs { block_number: Some(self.block + 1), network_id: self.simulator.0.chain_id.to_string(), from: self.request.from.unwrap_or_default(), - // TODO: error handling to: match &self.request.to.ok_or(ConversionError::MissingTo)? { TxKind::Create => Default::default(), TxKind::Call(to) => *to, @@ -436,7 +435,6 @@ impl EthCallInputs { .map(|bytes| bytes.to_vec()) .unwrap_or_default(), gas: self.request.gas, - gas_price: None, // use tenderly default for now value: self.request.value, simulation_type: Some(crate::tenderly::dto::SimulationType::Full), state_objects: Some( @@ -453,8 +451,10 @@ impl EthCallInputs { ), access_list: self.request.access_list.as_ref().map(Into::into), save: Some(true), + gas_price: None, // use tenderly default for now save_if_fails: Some(true), - ..Default::default() + transaction_index: None, + generate_access_list: None, }) } } From 1de44d917e5810b5f57efb1a085df4cf37efd45c Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 15:43:48 +0000 Subject: [PATCH 099/154] api improvements --- crates/simulator/src/simulation_builder.rs | 35 +++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index a568acfe56..694f61caf6 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -135,18 +135,27 @@ impl SimulationBuilder { self } - pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { - self.pre_interactions = interactions; + pub fn with_pre_interactions( + mut self, + interactions: impl IntoIterator, + ) -> Self { + self.pre_interactions = interactions.into_iter().collect(); self } - pub fn with_main_interactions(mut self, interactions: Vec) -> Self { - self.main_interactions = interactions; + pub fn with_main_interactions( + mut self, + interactions: impl IntoIterator, + ) -> Self { + self.main_interactions = interactions.into_iter().collect(); self } - pub fn with_post_interactions(mut self, interactions: Vec) -> Self { - self.post_interactions = interactions; + pub fn with_post_interactions( + mut self, + interactions: impl IntoIterator, + ) -> Self { + self.post_interactions = interactions.into_iter().collect(); self } @@ -338,13 +347,19 @@ impl Order { self } - pub fn with_pre_interactions(mut self, interactions: Vec) -> Self { - self.pre_interactions = interactions; + pub fn with_pre_interactions( + mut self, + interactions: impl IntoIterator, + ) -> Self { + self.pre_interactions = interactions.into_iter().collect(); self } - pub fn with_post_interactions(mut self, interactions: Vec) -> Self { - self.post_interactions = interactions; + pub fn with_post_interactions( + mut self, + interactions: impl IntoIterator, + ) -> Self { + self.post_interactions = interactions.into_iter().collect(); self } From 2afaf88a85d06449086b6d06ed2972739e8c5ac9 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 16:07:28 +0000 Subject: [PATCH 100/154] 2 price vector entries per order --- .../src/trade_verifier/mod.rs | 41 ++++--- crates/simulator/src/simulation_builder.rs | 18 ++- crates/simulator/src/simulation_encoding.rs | 110 +++++++++--------- 3 files changed, 85 insertions(+), 84 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 82e7632e7d..121893fd78 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -136,20 +136,29 @@ impl TradeVerifier { // the simulation to pass. Otherwise just use the solver address. let solver_address = trade.tx_origin().unwrap_or(trade.solver()); - let (tokens, clearing_prices) = match trade { - TradeKind::Legacy(_) => { - let tokens = vec![query.sell_token, query.buy_token]; - let prices = match query.kind { - OrderKind::Sell => { - vec![*out_amount, query.in_amount.get()] - } - OrderKind::Buy => { - vec![query.in_amount.get(), *out_amount] - } - }; - (tokens, prices) - } - TradeKind::Regular(trade) => trade.clearing_prices.iter().unzip(), + // `tokens` is passed to `Solver::swap` so it can measure balance changes; + // it is independent of the settlement's token/price vectors. + let tokens: Vec
= match trade { + TradeKind::Legacy(_) => vec![query.sell_token, query.buy_token], + TradeKind::Regular(trade) => trade.clearing_prices.keys().copied().collect(), + }; + let (fake_sell_price, fake_buy_price) = match trade { + TradeKind::Legacy(_) => match query.kind { + OrderKind::Sell => (*out_amount, query.in_amount.get()), + OrderKind::Buy => (query.in_amount.get(), *out_amount), + }, + TradeKind::Regular(trade) => ( + trade + .clearing_prices + .get(&query.sell_token) + .copied() + .unwrap_or(U256::ONE), + trade + .clearing_prices + .get(&query.buy_token) + .copied() + .unwrap_or(U256::ONE), + ), }; let (sell_amount, buy_amount) = match query.kind { @@ -240,8 +249,8 @@ impl TradeVerifier { .fill_at( ExecutionAmount::Full, PriceEncoding::Custom { - tokens: tokens.clone(), - clearing_prices, + sell_price: fake_sell_price, + buy_price: fake_buy_price, }, ); diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 694f61caf6..fa2fb5c032 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -237,7 +237,10 @@ impl SimulationBuilder { } /// Queues [`AccountOverrideRequest`]s to be resolved and applied during - /// [`build`](Self::build). Multiple requests may target the same address. + /// [`build`](Self::build). Multiple requests may target the same address + /// and will be applied on a best-effort basis (failure to compute balance + /// overrides or conflicting state overrides will get logged but do not + /// lead to an error). pub fn with_overrides( mut self, requests: impl IntoIterator, @@ -288,14 +291,11 @@ pub enum PriceEncoding { /// Sets `price[sell_token] = buy_amount` and `price[buy_token] = /// sell_amount`, exactly satisfying the order's limit with no surplus. LimitPrice, - /// Explicit token list and matching clearing prices. Use this when the - /// clearing prices differ from the order's limit — e.g. in trade + /// Explicit clearing prices for the order's sell and buy token. Use this + /// when the prices differ from the order's limit — e.g. in trade /// verification where the order amounts are set to always pass the limit /// check and the solver's quoted prices are supplied separately. - Custom { - tokens: Vec
, - clearing_prices: Vec, - }, + Custom { sell_price: U256, buy_price: U256 }, } /// A simulator-specific order that bundles the data needed to encode a trade. @@ -488,10 +488,6 @@ pub enum BuildError { NoOrder, #[error("no solver was set")] NoSolver, - #[error("sell token not found in token list")] - MissingSellToken, - #[error("buy token not found in token list")] - MissingBuyToken, #[error("failed to query filled amount from settlement contract: {0}")] FilledAmountQuery(#[source] anyhow::Error), #[error("failed to parse app data: {0}")] diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 1eb78cd76d..8ca6b67b7a 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -49,76 +49,72 @@ pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result ( - vec![first.data.sell_token, first.data.buy_token], - vec![first.data.buy_amount, first.data.sell_amount], - ), - PriceEncoding::Custom { - tokens, - clearing_prices, - } => (tokens.clone(), clearing_prices.clone()), - }; + // Each order occupies exactly 2 consecutive slots in the token/price + // vectors: [2*i] = sell_token, [2*i+1] = buy_token. + // This lets every order be encoded independently without requiring a shared + // global token list. + let n = builder.orders.len(); + let mut tokens = Vec::with_capacity(n * 2); + let mut clearing_prices = Vec::with_capacity(n * 2); + for order in &builder.orders { + let (sell_price, buy_price) = match &order.price_encoding { + PriceEncoding::LimitPrice => (order.data.buy_amount, order.data.sell_amount), + PriceEncoding::Custom { + sell_price, + buy_price, + } => (*sell_price, *buy_price), + }; + tokens.push(order.data.sell_token); + tokens.push(order.data.buy_token); + clearing_prices.push(sell_price); + clearing_prices.push(buy_price); + } - // Replace BuyTokensForBuffers placeholders using the first order's data. - // Must happen before clearing_prices is moved into EncodedSettlement. + // Expand any BuyTokensForBuffers request into one Balance override per + // order, then remove all BuyTokensForBuffers entries so duplicates are + // impossible. + if builder + .account_override_requests + .iter() + .any(|r| matches!(r, AccountOverrideRequest::BuyTokensForBuffers)) { - let first_exec = executed_amounts[0]; - let sell_idx = tokens - .iter() - .position(|t| *t == first.data.sell_token) - .ok_or(BuildError::MissingSellToken)?; - let buy_idx = tokens - .iter() - .position(|t| *t == first.data.buy_token) - .ok_or(BuildError::MissingBuyToken)?; - for request in &mut builder.account_override_requests { - if matches!(request, AccountOverrideRequest::BuyTokensForBuffers) { - let amount = match first.data.kind { - OrderKind::Sell => clearing_prices[sell_idx] - .saturating_mul(first_exec) - .checked_div(clearing_prices[buy_idx]) - .unwrap_or(U256::MAX), - OrderKind::Buy => first_exec, - } - // give 1 wei extra to avoid issues with rounding divisions - .saturating_add(U256::ONE); - - *request = AccountOverrideRequest::Balance { - holder: *builder.simulator.0.settlement.address(), - token: first.data.buy_token, - amount, - }; + builder + .account_override_requests + .retain(|r| !matches!(r, AccountOverrideRequest::BuyTokensForBuffers)); + let settlement = *builder.simulator.0.settlement.address(); + for (i, (order, &exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { + let sell_price = clearing_prices[2 * i]; + let buy_price = clearing_prices[2 * i + 1]; + let amount = match order.data.kind { + OrderKind::Sell => sell_price + .saturating_mul(exec) + .checked_div(buy_price) + .unwrap_or(U256::MAX), + OrderKind::Buy => exec, } + // give 1 wei extra to avoid issues with rounding divisions + .saturating_add(U256::ONE); + builder + .account_override_requests + .push(AccountOverrideRequest::Balance { + holder: settlement, + token: order.data.buy_token, + amount, + }); } } // Encode every order as a trade, then collect all their interactions. - let mut trades = Vec::with_capacity(builder.orders.len()); + let mut trades = Vec::with_capacity(n); let mut all_order_pre: Vec = vec![]; let mut all_order_post: Vec = vec![]; - for (order, exec) in builder.orders.iter().zip(&executed_amounts) { - let sell_idx = tokens - .iter() - .position(|t| *t == order.data.sell_token) - .ok_or(BuildError::MissingSellToken)?; - let buy_idx = tokens - .iter() - .position(|t| *t == order.data.buy_token) - .ok_or(BuildError::MissingBuyToken)?; + for (i, (order, exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { trades.push(encode_trade( &order.data, &order.signature, order.owner, - sell_idx, - buy_idx, + 2 * i, + 2 * i + 1, *exec, )); all_order_pre.extend_from_slice(&order.pre_interactions); From 439d719271a8c4209a2fb116b47807b51e582014 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 16:22:29 +0000 Subject: [PATCH 101/154] move gas_limit into settlement simulator --- crates/e2e/tests/e2e/quote_verification.rs | 2 +- crates/orderbook/src/run.rs | 5 +++-- crates/price-estimation/src/factory.rs | 2 +- crates/price-estimation/src/trade_verifier/mod.rs | 5 +---- crates/simulator/src/simulation_builder.rs | 7 +++++++ crates/simulator/src/simulation_encoding.rs | 3 ++- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 606f141bd9..89eaf9f071 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -199,6 +199,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { Default::default(), Default::default(), *onchain.contracts().weth.address(), + gas_limit, balance_overrides, block_stream.clone(), None, @@ -208,7 +209,6 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { let verifier = TradeVerifier::new( simulator, - gas_limit, None, Arc::new(web3.clone()), BigDecimal::zero(), diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 08b3c67d55..70c1797bb5 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -410,7 +410,7 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator2 = if let Some(config) = config.order_simulation { + let order_simulator = if let Some(config) = config.order_simulation { let tenderly: Option> = config.tenderly.as_ref().map(|tenderly_config| { Arc::new(simulator::tenderly::TenderlyApi::new( @@ -425,6 +425,7 @@ pub async fn run(config: Configuration) { Default::default(), hooks_trampoline_address, *native_token.address(), + config.gas_limit.saturating_to(), balance_overrider.clone(), current_block_stream.clone(), tenderly, @@ -444,7 +445,7 @@ pub async fn run(config: Configuration) { order_validator.clone(), app_data.clone(), config.active_order_competition_threshold, - order_simulator2, + order_simulator, )); check_database_connection(orderbook.as_ref()).await; diff --git a/crates/price-estimation/src/factory.rs b/crates/price-estimation/src/factory.rs index 4de8a55d71..05c90731e7 100644 --- a/crates/price-estimation/src/factory.rs +++ b/crates/price-estimation/src/factory.rs @@ -113,6 +113,7 @@ impl<'a> PriceEstimatorFactory<'a> { Default::default(), Default::default(), network.native_token, + args.max_gas_per_tx, balance_overrides, network.block_stream.clone(), tenderly.clone(), @@ -121,7 +122,6 @@ impl<'a> PriceEstimatorFactory<'a> { let verifier = TradeVerifier::new( simulator, - args.max_gas_per_tx, tenderly, components.code_fetcher.clone(), args.quote_inaccuracy_limit.clone(), diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 121893fd78..7bb29d2409 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -71,7 +71,6 @@ pub trait TradeVerifying: Send + Sync + 'static { pub struct TradeVerifier { tenderly: Option>, simulator: SettlementSimulator, - gas_limit: u64, code_fetcher: Arc, quote_inaccuracy_limit: BigRational, tokens_without_verification: HashSet
, @@ -86,7 +85,6 @@ impl TradeVerifier { #[expect(clippy::too_many_arguments)] pub fn new( simulator: SettlementSimulator, - gas_limit: u64, tenderly: Option>, code_fetcher: Arc, quote_inaccuracy_limit: BigDecimal, @@ -102,7 +100,6 @@ impl TradeVerifier { Self { tenderly, simulator, - gas_limit, code_fetcher, quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), tokens_without_verification, @@ -328,7 +325,7 @@ impl TradeVerifier { calldata, ) .from(solver_address) - .gas(self.gas_limit); + .gas(self.simulator.max_gas_limit()); let tx = swap_call.clone().into_transaction_request(); let result = swap_call diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index fa2fb5c032..d2bf6c7fc4 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -32,6 +32,7 @@ pub(crate) struct Inner { pub(crate) flash_loan_router: Address, pub(crate) hooks_trampoline: Address, pub(crate) native_token: Address, + pub(crate) max_gas_limit: u64, pub(crate) balance_overrides: Arc, pub(crate) provider: DynProvider, pub(crate) domain_separator: DomainSeparator, @@ -46,6 +47,7 @@ impl SettlementSimulator { flash_loan_router: Address, hooks_trampoline: Address, native_token: Address, + max_gas_limit: u64, balance_overrides: Arc, current_block: CurrentBlockWatcher, tenderly: Option>, @@ -60,6 +62,7 @@ impl SettlementSimulator { flash_loan_router, hooks_trampoline, native_token, + max_gas_limit, balance_overrides, provider, domain_separator, @@ -73,6 +76,10 @@ impl SettlementSimulator { self.0.native_token } + pub fn max_gas_limit(&self) -> u64 { + self.0.max_gas_limit + } + pub fn provider(&self) -> DynProvider { self.0.provider.clone() } diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 8ca6b67b7a..1dc8a04981 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -193,6 +193,7 @@ pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result Date: Thu, 30 Apr 2026 16:37:41 +0000 Subject: [PATCH 102/154] sort dependencies --- crates/simulator/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/simulator/Cargo.toml b/crates/simulator/Cargo.toml index 1cb1960dfe..5c41b774e0 100644 --- a/crates/simulator/Cargo.toml +++ b/crates/simulator/Cargo.toml @@ -16,7 +16,6 @@ alloy-sol-types = { workspace = true } alloy-transport = { workspace = true } anyhow = { workspace = true } app-data = { workspace = true } -futures = { workspace = true } async-trait = { workspace = true } balance-overrides = { workspace = true } cached = { workspace = true } @@ -27,6 +26,7 @@ contracts = { workspace = true } derive_more = { workspace = true } eth-domain-types = { workspace = true } ethrpc = { workspace = true } +futures = { workspace = true } gas-price-estimation = { workspace = true } hex-literal = { workspace = true } http-client = { workspace = true } From 543d466fa8b2b772736031bce7eb3103b4d67b2e Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 30 Apr 2026 16:41:26 +0000 Subject: [PATCH 103/154] fix lints --- crates/price-estimation/src/trade_verifier/mod.rs | 1 - crates/simulator/src/simulation_builder.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 7bb29d2409..ea4274328e 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -82,7 +82,6 @@ impl TradeVerifier { const SPARDOSE: Address = address!("0000000000000000000000000000000000020000"); const TRADER_IMPL: Address = address!("0000000000000000000000000000000000010000"); - #[expect(clippy::too_many_arguments)] pub fn new( simulator: SettlementSimulator, tenderly: Option>, diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index d2bf6c7fc4..38ad3fef16 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -42,6 +42,7 @@ pub(crate) struct Inner { } impl SettlementSimulator { + #[expect(clippy::too_many_arguments)] pub async fn new( settlement: contracts::GPv2Settlement::Instance, flash_loan_router: Address, From 9338743002f7e1c614d58b1b83c5a0a0779d16bc Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 5 May 2026 10:59:55 +0100 Subject: [PATCH 104/154] Migrate orderbook + aave_replay to new simulator API Wires the call sites that the merge from main left broken: - `SettlementSimulator::new` now takes `native_token` and `max_gas_limit`. - `Prices::Limit` is gone, set per-order via `Order::fill_at(_, PriceEncoding::LimitPrice)`. - `add_order` -> `add_orders`, `with_override` -> `with_overrides`. Also collapses the two `SettlementSimulator` instances in `orderbook/run.rs` into one shared by the EIP-1271 adapter and the orderbook. Side-effect: the orderbook's order simulator now gets the real FlashLoanRouter address instead of the zero default the second build site was passing. --- crates/orderbook/src/eip1271_simulation.rs | 13 ++-- crates/orderbook/src/run.rs | 91 +++++++++------------- crates/simulator/tests/aave_replay.rs | 15 ++-- 3 files changed, 47 insertions(+), 72 deletions(-) diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index b8326501d8..c98eda3b87 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -8,7 +8,7 @@ use { AccountOverrideRequest, Block, ExecutionAmount, - Prices, + PriceEncoding, SettlementSimulator, Solver, }, @@ -37,16 +37,13 @@ impl Eip1271Simulating for OrderSimulatorAdapter { let inputs = self .inner .new_simulation_builder() - .add_order( - simulation_builder::Order::new(order.data) - .with_signature(order.metadata.owner, order.signature.clone()) - .with_executed_amount(ExecutionAmount::Full), - ) + .add_orders([simulation_builder::Order::new(order.data) + .with_signature(order.metadata.owner, order.signature.clone()) + .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) .parameters_from_app_data(&full_app_data) .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("parse app data")))? - .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) - .with_override(AccountOverrideRequest::BuyTokensForBuffers) + .with_overrides([AccountOverrideRequest::BuyTokensForBuffers]) .at_block(Block::Latest) .build() .await diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 9f0d12722a..a172266e55 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -380,7 +380,7 @@ pub async fn run(config: Configuration) { .await .ok(); - let (order_simulator, eip1271_simulator) = match &config.order_simulation { + let order_simulator = match &config.order_simulation { Some(sim_config) => { let tenderly: Option> = sim_config.tenderly.as_ref().map(|tenderly_config| { @@ -392,39 +392,45 @@ pub async fn run(config: Configuration) { }); let flash_loan_router_address = contracts::FlashLoanRouter::deployment_address(&chain.id()).unwrap_or_default(); - let order_simulator = simulator::simulation_builder::SettlementSimulator::new( - settlement_contract.clone(), - flash_loan_router_address, - hooks_trampoline_address, - balance_overrider.clone(), - current_block_stream.clone(), - tenderly, + Some( + simulator::simulation_builder::SettlementSimulator::new( + settlement_contract.clone(), + flash_loan_router_address, + hooks_trampoline_address, + *native_token.address(), + sim_config.gas_limit.saturating_to(), + balance_overrider.clone(), + current_block_stream.clone(), + tenderly, + ) + .await + .expect("failed to create SettlementSimulator"), ) - .await - .expect("failed to create SettlementSimulator"); + } + None => None, + }; + + let eip1271_simulator = config + .order_simulation + .as_ref() + .zip(order_simulator.clone()) + .and_then(|(sim_config, simulator)| { let mode = match sim_config.eip1271_simulation_mode { - configs::orderbook::Eip1271SimulationMode::Shadow => { - Some(Eip1271SimulationMode::Shadow) - } + configs::orderbook::Eip1271SimulationMode::Shadow => Eip1271SimulationMode::Shadow, configs::orderbook::Eip1271SimulationMode::Enforce => { - Some(Eip1271SimulationMode::Enforce) + Eip1271SimulationMode::Enforce } - configs::orderbook::Eip1271SimulationMode::Disabled => None, + configs::orderbook::Eip1271SimulationMode::Disabled => return None, }; - let eip1271_simulator = mode.map(|mode| { - let simulator: Arc = Arc::new( - crate::eip1271_simulation::OrderSimulatorAdapter::new(order_simulator.clone()), - ); - Eip1271Simulator { - simulator, - mode, - timeout: sim_config.eip1271_simulation_timeout, - } - }); - (Some(order_simulator), eip1271_simulator) - } - None => (None, None), - }; + let simulator: Arc = Arc::new( + crate::eip1271_simulation::OrderSimulatorAdapter::new(simulator), + ); + Some(Eip1271Simulator { + simulator, + mode, + timeout: sim_config.eip1271_simulation_timeout, + }) + }); let order_validator = Arc::new(OrderValidator::new( native_token.clone(), @@ -463,33 +469,6 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator = if let Some(config) = config.order_simulation { - let tenderly: Option> = - config.tenderly.as_ref().map(|tenderly_config| { - Arc::new(simulator::tenderly::TenderlyApi::new( - tenderly_config, - &http_factory, - chain.id().to_string(), - )) as _ - }); - Some( - simulator::simulation_builder::SettlementSimulator::new( - settlement_contract.clone(), - Default::default(), - hooks_trampoline_address, - *native_token.address(), - config.gas_limit.saturating_to(), - balance_overrider.clone(), - current_block_stream.clone(), - tenderly, - ) - .await - .unwrap(), - ) - } else { - None - }; - let orderbook = Arc::new(Orderbook::new( domain_separator, *settlement_contract.address(), diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index ba41dc8f64..6be360f2b1 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -11,7 +11,7 @@ use { Block, EthCallInputs, ExecutionAmount, - Prices, + PriceEncoding, SettlementSimulator, Solver, }, @@ -180,6 +180,8 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI settlement, flash_loan_router, hooks_trampoline, + sell_token_weth, + 30_000_000u64, balance_overrider, block_stream, None, @@ -208,16 +210,13 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI simulator .new_simulation_builder() - .add_order( - simulation_builder::Order::new(order_data) - .with_signature(order_owner, Signature::Eip1271(signature_bytes)) - .with_executed_amount(ExecutionAmount::Full), - ) + .add_orders([simulation_builder::Order::new(order_data) + .with_signature(order_owner, Signature::Eip1271(signature_bytes)) + .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) .parameters_from_app_data(full_app_data) .expect("parameters_from_app_data should parse the app data") - .with_prices(Prices::Limit) .from_solver(Solver::Fake(None)) - .with_override(AccountOverrideRequest::BuyTokensForBuffers) + .with_overrides([AccountOverrideRequest::BuyTokensForBuffers]) .at_block(Block::Number(fork_block_mainnet)) .build() .await From e24104d913930d2d1d55fbd9617b5c254c95ce8a Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 12:41:20 +0000 Subject: [PATCH 105/154] Keep error reporting consistent --- crates/e2e/tests/e2e/malformed_requests.rs | 9 ++++++++- crates/orderbook/src/orderbook.rs | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/e2e/tests/e2e/malformed_requests.rs b/crates/e2e/tests/e2e/malformed_requests.rs index f6ccc40a66..62c6f95535 100644 --- a/crates/e2e/tests/e2e/malformed_requests.rs +++ b/crates/e2e/tests/e2e/malformed_requests.rs @@ -460,7 +460,7 @@ async fn http_validation(web3: Web3) { "zero sellAmount should return 422" ); - // Invalid kind enum value → 422 + // some fields missing → 422 let response = client .post(format!("{API_HOST}/restricted/api/v1/debug/simulation")) .json(&json!({ @@ -492,6 +492,13 @@ async fn http_validation(web3: Web3) { "kind": "sell", "owner": VALID_ADDRESS, "appData": bad_app_data, + "sellTokenBalance": "erc20", + "buyTokenBalance": "erc20", + "signingScheme": "eip1271", + "signature": "0x000000", + "feeAmount": "0", + "validTo": 12341234, + "partiallyFillable": false, })) .send() .await diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 16e6a1802a..2a9f51bb82 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -646,7 +646,9 @@ impl Orderbook { simulation_builder::PriceEncoding::LimitPrice, )]) .parameters_from_app_data(&full_app_data) - .context("failed to parse app data")? + .map_err(|err| { + OrderSimulationError::MalformedInput(anyhow!("app_data `{}`: {err}", full_app_data)) + })? .at_block( block_number .map(simulation_builder::Block::Number) @@ -703,7 +705,12 @@ impl Orderbook { simulation_builder::PriceEncoding::LimitPrice, )]) .parameters_from_app_data(&request.app_data) - .context("failed to parse app data")? + .map_err(|err| { + OrderSimulationError::MalformedInput(anyhow!( + "app_data `{}`: {err}", + request.app_data + )) + })? .at_block( request .block_number From 6ff31cb119c154b17aeefd40a2b8f8743ccdcb0b Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 12:48:12 +0000 Subject: [PATCH 106/154] fix other failing e2e test --- crates/e2e/tests/e2e/malformed_requests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/e2e/tests/e2e/malformed_requests.rs b/crates/e2e/tests/e2e/malformed_requests.rs index 62c6f95535..dff0c2ddb8 100644 --- a/crates/e2e/tests/e2e/malformed_requests.rs +++ b/crates/e2e/tests/e2e/malformed_requests.rs @@ -570,6 +570,14 @@ async fn simulation_not_enabled(web3: Web3) { "buyAmount": "1000000000000000000", "kind": "sell", "owner": VALID_ADDRESS, + "appData": "{}", + "sellTokenBalance": "erc20", + "buyTokenBalance": "erc20", + "signingScheme": "eip1271", + "signature": "0x000000", + "feeAmount": "0", + "validTo": 12341234, + "partiallyFillable": false, })) .send() .await From bd726e58f9ffb9e6d9ab9ab9f7d8a707830c22e3 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:29:41 +0000 Subject: [PATCH 107/154] Pipe contract addresses around --- crates/autopilot/src/infra/blockchain/contracts.rs | 13 +++++++++++++ crates/autopilot/src/run.rs | 3 +++ crates/configs/src/shared.rs | 3 +++ crates/e2e/src/setup/services.rs | 5 +++++ crates/orderbook/src/run.rs | 12 +++++++++++- crates/price-estimation/src/factory.rs | 6 ++++-- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index 27a3ebf298..0b9a676214 100644 --- a/crates/autopilot/src/infra/blockchain/contracts.rs +++ b/crates/autopilot/src/infra/blockchain/contracts.rs @@ -3,6 +3,7 @@ use { chain::Chain, contracts::{ ChainalysisOracle, + FlashLoanRouter, GPv2AllowListAuthentication, GPv2Settlement, HooksTrampoline, @@ -21,6 +22,7 @@ pub struct Contracts { balances: Balances::Instance, chainalysis_oracle: Option, trampoline: HooksTrampoline::Instance, + flashloan_router: Address, /// The authenticator contract that decides which solver is allowed to /// submit settlements. @@ -36,6 +38,7 @@ pub struct Addresses { pub weth: Option
, pub balances: Option
, pub trampoline: Option
, + pub flashloan_router: Option
, } impl Contracts { @@ -80,6 +83,11 @@ impl Contracts { web3.provider.clone(), ); + let flashloan_router = addresses + .flashloan_router + .or_else(|| FlashLoanRouter::deployment_address(&chain.id())) + .unwrap(); + let chainalysis_oracle = ChainalysisOracle::Instance::deployed(&web3.provider) .await .ok(); @@ -111,6 +119,7 @@ impl Contracts { settlement_domain_separator, authenticator, trampoline, + flashloan_router, } } @@ -151,4 +160,8 @@ impl Contracts { pub fn authenticator(&self) -> &GPv2AllowListAuthentication::Instance { &self.authenticator } + + pub fn flashloan_router(&self) -> Address { + self.flashloan_router + } } diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 1f0482cf66..ddb7ccaaff 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -221,6 +221,7 @@ pub async fn run(config: Configuration, shutdown_controller: ShutdownController) weth: config.shared.contracts.native_token, balances: config.shared.contracts.balances, trampoline: config.shared.contracts.hooks, + flashloan_router: config.shared.contracts.flashloan_router, }; let current_block_args = shared::current_block::Arguments::from(&config.shared.current_block); let eth = ethereum( @@ -314,6 +315,8 @@ pub async fn run(config: Configuration, shutdown_controller: ShutdownController) .await .expect("failed to query solver authenticator address"), block_stream: eth.current_block().clone(), + flash_loan_router: eth.contracts().flashloan_router(), + hooks_trampoline: *eth.contracts().trampoline().address(), }, factory::Components { http_factory: http_client::HttpClientFactory::new(&configs::http_client::HttpClient { diff --git a/crates/configs/src/shared.rs b/crates/configs/src/shared.rs index 3725a55b66..237f28dc82 100644 --- a/crates/configs/src/shared.rs +++ b/crates/configs/src/shared.rs @@ -182,6 +182,9 @@ pub struct ContractAddresses { /// The Balancer V2 Vault contract used for liquidity sourcing. pub balancer_v2_vault: Option
, + + /// The flashloan router contract. + pub flashloan_router: Option
, } /// Logging configuration (log filter, output format). diff --git a/crates/e2e/src/setup/services.rs b/crates/e2e/src/setup/services.rs index 10210baf7b..7bfe9b238b 100644 --- a/crates/e2e/src/setup/services.rs +++ b/crates/e2e/src/setup/services.rs @@ -143,6 +143,11 @@ impl<'a> Services<'a> { native_token: Some(*self.contracts.weth.address()), hooks: Some(*self.contracts.hooks.address()), balancer_v2_vault: Some(*self.contracts.balancer_vault.address()), + flashloan_router: self + .contracts + .flashloan_router + .as_ref() + .map(|c| *c.address()), }, ..Default::default() } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 70c1797bb5..4e498f759d 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -19,6 +19,7 @@ use { contracts::{ BalancerV2Vault, ChainalysisOracle, + FlashLoanRouter, GPv2Settlement, HooksTrampoline, WETH9, @@ -171,6 +172,13 @@ pub async fn run(config: Configuration) { }; let hooks_trampoline_address = *hooks_contract.address(); + let flashloan_router_address = config + .shared + .contracts + .flashloan_router + .or_else(|| FlashLoanRouter::deployment_address(&chain_id)) + .expect("no flashloan router deployment for this chain"); + verify_deployed_contract_constants(&settlement_contract, chain_id) .await .expect("Deployed contract constants don't match the ones in this binary"); @@ -247,6 +255,8 @@ pub async fn run(config: Configuration) { .await .expect("failed to query solver authenticator address"), block_stream: current_block_stream.clone(), + flash_loan_router: flashloan_router_address, + hooks_trampoline: hooks_trampoline_address, }, factory::Components { http_factory: http_client::HttpClientFactory::new(&configs::http_client::HttpClient { @@ -422,7 +432,7 @@ pub async fn run(config: Configuration) { Some( simulator::simulation_builder::SettlementSimulator::new( settlement_contract.clone(), - Default::default(), + flashloan_router_address, hooks_trampoline_address, *native_token.address(), config.gas_limit.saturating_to(), diff --git a/crates/price-estimation/src/factory.rs b/crates/price-estimation/src/factory.rs index 05c90731e7..3ef3f3add4 100644 --- a/crates/price-estimation/src/factory.rs +++ b/crates/price-estimation/src/factory.rs @@ -49,6 +49,8 @@ pub struct Network { pub settlement: Address, pub authenticator: Address, pub block_stream: CurrentBlockWatcher, + pub flash_loan_router: Address, + pub hooks_trampoline: Address, } /// The shared components needed for creating price estimators. @@ -110,8 +112,8 @@ impl<'a> PriceEstimatorFactory<'a> { GPv2Settlement::GPv2Settlement::new(network.settlement, web3.provider.clone()); let simulator = SettlementSimulator::new( settlement_contract, - Default::default(), - Default::default(), + network.flash_loan_router, + network.hooks_trampoline, network.native_token, args.max_gas_per_tx, balance_overrides, From 2b87af21bf54817d1afb245de2dd101dc7025421 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:31:31 +0000 Subject: [PATCH 108/154] add link for storage slot encoding --- crates/simulator/src/simulation_encoding.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index 1dc8a04981..f5c8ac22eb 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -251,6 +251,7 @@ async fn build_final_state_overrides( // GPv2AllowListAuthentication stores `mapping(address => bool) managers` // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ // slot_padded). + // let mut buf = [0u8; 64]; buf[12..32].copy_from_slice(addr.as_slice()); buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); From bf12ca86045ebccdcffdea0900269853ff09cb85 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:34:17 +0000 Subject: [PATCH 109/154] `.unwrap()` -> `.expect()` --- crates/orderbook/src/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 4e498f759d..c55abfb4b3 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -441,7 +441,7 @@ pub async fn run(config: Configuration) { tenderly, ) .await - .unwrap(), + .expect("failed to initialize SettlementSimulator"), ) } else { None From 2010b59ae74d9985f3bf4bbda37b6d3ed0f33d22 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:36:51 +0000 Subject: [PATCH 110/154] replace unreachable with error log --- crates/simulator/src/simulation_encoding.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs index f5c8ac22eb..f74eba2eb5 100644 --- a/crates/simulator/src/simulation_encoding.rs +++ b/crates/simulator/src/simulation_encoding.rs @@ -280,10 +280,9 @@ async fn build_final_state_overrides( result } AccountOverrideRequest::BuyTokensForBuffers => { - unreachable!( - "replaced with specific Balance requests before state overrides get \ - computed" - ) + tracing::error!("BuyTokensForBuffers is supposed to be replaced with specific balance \ + override requests before assembling the final state overrides"); + None } AccountOverrideRequest::Code { account, code } => Some(( account, From 68bdd8d8052210c4dff8f06bd88a376930b480d2 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:42:42 +0000 Subject: [PATCH 111/154] Move new encoding logic into existing file --- crates/simulator/src/encoding.rs | 375 ++++++++++++++++++++- crates/simulator/src/lib.rs | 1 - crates/simulator/src/simulation_builder.rs | 2 +- 3 files changed, 375 insertions(+), 3 deletions(-) diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index 61b3db0105..aa2eef46cf 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -1,7 +1,25 @@ use { - alloy_primitives::{Address, B256, Bytes, U256}, + crate::simulation_builder::{ + AccountOverrideRequest, + Block, + BuildError, + EthCallInputs, + ExecutionAmount, + MergeConflict, + Order, + PriceEncoding, + SimulationBuilder, + Solver, + WrapperConfig, + }, + alloy_primitives::{Address, B256, Bytes, U256, keccak256}, + alloy_rpc_types::{ + TransactionRequest, + state::{AccountOverride, StateOverride}, + }, alloy_sol_types::SolCall, app_data::AppDataHash, + balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, contracts::GPv2Settlement, derive_more::Debug, model::{ @@ -325,3 +343,358 @@ pub fn encode_wrapper_data(wrappers: &[WrapperCall]) -> Bytes { wrapper_data.into() } + +pub(crate) async fn finish_simulation_builder( + mut builder: SimulationBuilder, +) -> Result { + if builder.orders.is_empty() { + return Err(BuildError::NoOrder); + } + + let block = match builder.block { + Block::Latest => builder.simulator.0.current_block.borrow().number, + Block::Number(n) => n, + }; + + let executed_amounts = futures::future::try_join_all( + builder + .orders + .iter() + .map(|o| executed_amount(&builder, o, block)), + ) + .await?; + + // Each order occupies exactly 2 consecutive slots in the token/price + // vectors: [2*i] = sell_token, [2*i+1] = buy_token. + // This lets every order be encoded independently without requiring a shared + // global token list. + let n = builder.orders.len(); + let mut tokens = Vec::with_capacity(n * 2); + let mut clearing_prices = Vec::with_capacity(n * 2); + for order in &builder.orders { + let (sell_price, buy_price) = match &order.price_encoding { + PriceEncoding::LimitPrice => (order.data.buy_amount, order.data.sell_amount), + PriceEncoding::Custom { + sell_price, + buy_price, + } => (*sell_price, *buy_price), + }; + tokens.push(order.data.sell_token); + tokens.push(order.data.buy_token); + clearing_prices.push(sell_price); + clearing_prices.push(buy_price); + } + + // Expand any BuyTokensForBuffers request into one Balance override per + // order, then remove all BuyTokensForBuffers entries so duplicates are + // impossible. + if builder + .account_override_requests + .iter() + .any(|r| matches!(r, AccountOverrideRequest::BuyTokensForBuffers)) + { + builder + .account_override_requests + .retain(|r| !matches!(r, AccountOverrideRequest::BuyTokensForBuffers)); + let settlement = *builder.simulator.0.settlement.address(); + for (i, (order, &exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { + let sell_price = clearing_prices[2 * i]; + let buy_price = clearing_prices[2 * i + 1]; + let amount = match order.data.kind { + OrderKind::Sell => sell_price + .saturating_mul(exec) + .checked_div(buy_price) + .unwrap_or(U256::MAX), + OrderKind::Buy => exec, + } + // give 1 wei extra to avoid issues with rounding divisions + .saturating_add(U256::ONE); + builder + .account_override_requests + .push(AccountOverrideRequest::Balance { + holder: settlement, + token: order.data.buy_token, + amount, + }); + } + } + + // Encode every order as a trade, then collect all their interactions. + let mut trades = Vec::with_capacity(n); + let mut all_order_pre: Vec = vec![]; + let mut all_order_post: Vec = vec![]; + for (i, (order, exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { + trades.push(encode_trade( + &order.data, + &order.signature, + order.owner, + 2 * i, + 2 * i + 1, + *exec, + )); + all_order_pre.extend_from_slice(&order.pre_interactions); + all_order_post.extend_from_slice(&order.post_interactions); + } + + let settlement = EncodedSettlement { + tokens, + clearing_prices, + trades, + interactions: Interactions { + // order pre-hooks run before any additional pre-interactions + pre: encode_interactions(all_order_pre.iter().chain(&builder.pre_interactions)), + main: encode_interactions(&builder.main_interactions), + // additional post-interactions run before order post-hooks + post: encode_interactions(builder.post_interactions.iter().chain(&all_order_post)), + }, + }; + + let settle_calldata = { + let mut bytes = settlement.into_settle_call().to_vec(); + if let Some(id) = builder.auction_id { + bytes.extend_from_slice(&id.to_be_bytes()); + } + bytes.into() + }; + + let wrapper = builder.wrapper; + let (to, input) = match wrapper { + WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => { + encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") + } + WrapperConfig::Flashloan(loans) => { + let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { + loans: loans + .into_iter() + .map(|l| contracts::FlashLoanRouter::LoanRequest::Data { + amount: l.amount, + borrower: l.borrower, + lender: l.lender, + token: l.token, + }) + .collect(), + settlement: settle_calldata, + } + .abi_encode() + .into(); + (builder.simulator.0.flash_loan_router, calldata) + } + _ => (*builder.simulator.0.settlement.address(), settle_calldata), + }; + + let from = match builder.solver { + Some(Solver::OriginUnaltered(addr)) => addr, + Some(Solver::Fake(opt)) => { + let addr = opt.unwrap_or_else(Address::random); + builder + .account_override_requests + .push(AccountOverrideRequest::SufficientEthBalance(addr)); + builder + .account_override_requests + .push(AccountOverrideRequest::AuthenticateAsSolver(addr)); + addr + } + None => return Err(BuildError::NoSolver), + }; + let state_overrides = build_final_state_overrides( + builder.account_override_requests, + builder.simulator.0.balance_overrides.as_ref(), + builder.simulator.0.authenticator, + ) + .await; + + Ok(EthCallInputs { + request: TransactionRequest { + from: Some(from), + to: Some(to.into()), + input: input.into(), + gas: Some(builder.simulator.0.max_gas_limit), + ..Default::default() + }, + state_overrides, + block, + simulator: builder.simulator, + }) +} + +async fn executed_amount( + builder: &SimulationBuilder, + order: &Order, + block: u64, +) -> Result { + let full = match order.data.kind { + OrderKind::Sell => order.data.sell_amount, + OrderKind::Buy => order.data.buy_amount, + }; + + Ok(match order.executed_amount { + ExecutionAmount::Full => full, + ExecutionAmount::Explicit(amount) => amount, + ExecutionAmount::Remaining => { + let uid = order + .data + .uid(&builder.simulator.0.domain_separator, order.owner); + let filled_amount = builder + .simulator + .0 + .settlement + .filledAmount(Bytes::from(uid.0)) + .block(block.into()) + .call() + .await + .map_err(|err| BuildError::FilledAmountQuery(err.into()))?; + full.saturating_sub(filled_amount) + } + }) +} + +/// Resolves all [`AccountOverrideRequest`]s concurrently on a best-effort +/// basis. Failures are logged and the corresponding override is skipped rather +/// than aborting the whole build. +async fn build_final_state_overrides( + requests: Vec, + balance_overrides: &dyn BalanceOverriding, + authenticator: Address, +) -> StateOverride { + let futures = requests.into_iter().map(|request| async move { + match request { + AccountOverrideRequest::SufficientEthBalance(addr) => Some(( + addr, + AccountOverride::default().with_balance(U256::MAX / U256::from(2)), + )), + AccountOverrideRequest::AuthenticateAsSolver(addr) => { + // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ + // slot_padded). + // + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(addr.as_slice()); + buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); + let slot = keccak256(buf); + Some(( + authenticator, + AccountOverride::default() + .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), + )) + } + AccountOverrideRequest::Balance { + holder, + token, + amount, + } => { + let result = balance_overrides + .state_override(BalanceOverrideRequest { + token, + holder, + amount, + }) + .await; + if result.is_none() { + tracing::warn!(%token, %holder, "failed to compute balance state override, skipping"); + } + result + } + AccountOverrideRequest::BuyTokensForBuffers => { + tracing::error!("BuyTokensForBuffers is supposed to be replaced with specific balance \ + override requests before assembling the final state overrides"); + None + } + AccountOverrideRequest::Code { account, code } => Some(( + account, + AccountOverride { + code: Some(code), + ..Default::default() + }, + )), + AccountOverrideRequest::Custom { account, state } => Some((account, state)), + } + }); + + let mut state_overrides = StateOverride::default(); + for (address, account_override) in futures::future::join_all(futures) + .await + .into_iter() + .flatten() + { + if let Err(err) = apply_account_override(&mut state_overrides, address, account_override) { + tracing::warn!(?err, %address, "conflicting state overrides for address, skipping"); + } + } + state_overrides +} + +/// Merges `new` into `existing` field by field. +/// +/// Returns [`MergeConflict`] if both overrides write the same field. +/// Non-conflicting `state_diff` entries are combined into a single map. +fn merge_account_override( + existing: &mut AccountOverride, + new: AccountOverride, +) -> Result<(), MergeConflict> { + if new.balance.is_some() { + if existing.balance.is_some() { + return Err(MergeConflict::Balance); + } + existing.balance = new.balance; + } + if new.nonce.is_some() { + if existing.nonce.is_some() { + return Err(MergeConflict::Nonce); + } + existing.nonce = new.nonce; + } + if new.code.is_some() { + if existing.code.is_some() { + return Err(MergeConflict::Code); + } + existing.code = new.code; + } + match (new.state, new.state_diff) { + (Some(new_state), None) => { + if existing.state.is_some() { + return Err(MergeConflict::State); + } + if existing.state_diff.is_some() { + return Err(MergeConflict::StateAndStateDiff); + } + existing.state = Some(new_state); + } + (None, Some(new_diff)) => { + if existing.state.is_some() { + return Err(MergeConflict::StateAndStateDiff); + } + match &mut existing.state_diff { + None => existing.state_diff = Some(new_diff), + Some(existing_diff) => { + for (slot, value) in new_diff { + if existing_diff.contains_key(&slot) { + return Err(MergeConflict::StateDiffSlot(slot)); + } + existing_diff.insert(slot, value); + } + } + } + } + (None, None) => {} + // alloy does not allow both simultaneously, treat as incompatible + (Some(_), Some(_)) => return Err(MergeConflict::StateAndStateDiff), + } + Ok(()) +} + +/// Applies `new` to the override map for `address`. +/// +/// If `address` already has an entry, the overrides are merged via +/// [`merge_account_override`]. Returns an error on conflict. +fn apply_account_override( + overrides: &mut StateOverride, + address: Address, + new: AccountOverride, +) -> Result<(), MergeConflict> { + if let Some(existing) = overrides.get_mut(&address) { + merge_account_override(existing, new) + } else { + overrides.insert(address, new); + Ok(()) + } +} diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index ac90a39592..78856d3031 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -1,7 +1,6 @@ pub mod encoding; pub mod ethereum; pub mod simulation_builder; -mod simulation_encoding; pub mod tenderly; mod utils; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 38ad3fef16..4272161c9b 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -261,7 +261,7 @@ impl SimulationBuilder { pub async fn build(self) -> Result { // Forward to a helper function to split the boring repetitive builder // code from the non-trivial code that actually does the encoding. - crate::simulation_encoding::encode(self).await + crate::encoding::finish_simulation_builder(self).await } } From 6d873083526558f84f712e231bc6171059f06c64 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:54:37 +0000 Subject: [PATCH 112/154] delete unused file --- crates/simulator/src/simulation_encoding.rs | 385 -------------------- 1 file changed, 385 deletions(-) delete mode 100644 crates/simulator/src/simulation_encoding.rs diff --git a/crates/simulator/src/simulation_encoding.rs b/crates/simulator/src/simulation_encoding.rs deleted file mode 100644 index f74eba2eb5..0000000000 --- a/crates/simulator/src/simulation_encoding.rs +++ /dev/null @@ -1,385 +0,0 @@ -use { - crate::{ - encoding::{ - EncodedSettlement, - Interactions, - encode_interactions, - encode_trade, - encode_wrapper_settlement, - }, - simulation_builder::{ - AccountOverrideRequest, - Block, - BuildError, - EthCallInputs, - ExecutionAmount, - MergeConflict, - Order, - PriceEncoding, - SimulationBuilder, - Solver, - WrapperConfig, - }, - }, - alloy_primitives::{Address, B256, Bytes, U256, keccak256}, - alloy_rpc_types::{ - TransactionRequest, - state::{AccountOverride, StateOverride}, - }, - alloy_sol_types::SolCall, - balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, - model::{interaction::InteractionData, order::OrderKind}, -}; - -pub(crate) async fn encode(mut builder: SimulationBuilder) -> Result { - if builder.orders.is_empty() { - return Err(BuildError::NoOrder); - } - - let block = match builder.block { - Block::Latest => builder.simulator.0.current_block.borrow().number, - Block::Number(n) => n, - }; - - let executed_amounts = futures::future::try_join_all( - builder - .orders - .iter() - .map(|o| executed_amount(&builder, o, block)), - ) - .await?; - - // Each order occupies exactly 2 consecutive slots in the token/price - // vectors: [2*i] = sell_token, [2*i+1] = buy_token. - // This lets every order be encoded independently without requiring a shared - // global token list. - let n = builder.orders.len(); - let mut tokens = Vec::with_capacity(n * 2); - let mut clearing_prices = Vec::with_capacity(n * 2); - for order in &builder.orders { - let (sell_price, buy_price) = match &order.price_encoding { - PriceEncoding::LimitPrice => (order.data.buy_amount, order.data.sell_amount), - PriceEncoding::Custom { - sell_price, - buy_price, - } => (*sell_price, *buy_price), - }; - tokens.push(order.data.sell_token); - tokens.push(order.data.buy_token); - clearing_prices.push(sell_price); - clearing_prices.push(buy_price); - } - - // Expand any BuyTokensForBuffers request into one Balance override per - // order, then remove all BuyTokensForBuffers entries so duplicates are - // impossible. - if builder - .account_override_requests - .iter() - .any(|r| matches!(r, AccountOverrideRequest::BuyTokensForBuffers)) - { - builder - .account_override_requests - .retain(|r| !matches!(r, AccountOverrideRequest::BuyTokensForBuffers)); - let settlement = *builder.simulator.0.settlement.address(); - for (i, (order, &exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { - let sell_price = clearing_prices[2 * i]; - let buy_price = clearing_prices[2 * i + 1]; - let amount = match order.data.kind { - OrderKind::Sell => sell_price - .saturating_mul(exec) - .checked_div(buy_price) - .unwrap_or(U256::MAX), - OrderKind::Buy => exec, - } - // give 1 wei extra to avoid issues with rounding divisions - .saturating_add(U256::ONE); - builder - .account_override_requests - .push(AccountOverrideRequest::Balance { - holder: settlement, - token: order.data.buy_token, - amount, - }); - } - } - - // Encode every order as a trade, then collect all their interactions. - let mut trades = Vec::with_capacity(n); - let mut all_order_pre: Vec = vec![]; - let mut all_order_post: Vec = vec![]; - for (i, (order, exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { - trades.push(encode_trade( - &order.data, - &order.signature, - order.owner, - 2 * i, - 2 * i + 1, - *exec, - )); - all_order_pre.extend_from_slice(&order.pre_interactions); - all_order_post.extend_from_slice(&order.post_interactions); - } - - let settlement = EncodedSettlement { - tokens, - clearing_prices, - trades, - interactions: Interactions { - // order pre-hooks run before any additional pre-interactions - pre: encode_interactions(all_order_pre.iter().chain(&builder.pre_interactions)), - main: encode_interactions(&builder.main_interactions), - // additional post-interactions run before order post-hooks - post: encode_interactions(builder.post_interactions.iter().chain(&all_order_post)), - }, - }; - - let settle_calldata = { - let mut bytes = settlement.into_settle_call().to_vec(); - if let Some(id) = builder.auction_id { - bytes.extend_from_slice(&id.to_be_bytes()); - } - bytes.into() - }; - - let wrapper = builder.wrapper; - let (to, input) = match wrapper { - WrapperConfig::Custom(wrappers) if !wrappers.is_empty() => { - encode_wrapper_settlement(&wrappers, settle_calldata).expect("wrappers is non-empty") - } - WrapperConfig::Flashloan(loans) => { - let calldata = contracts::FlashLoanRouter::FlashLoanRouter::flashLoanAndSettleCall { - loans: loans - .into_iter() - .map(|l| contracts::FlashLoanRouter::LoanRequest::Data { - amount: l.amount, - borrower: l.borrower, - lender: l.lender, - token: l.token, - }) - .collect(), - settlement: settle_calldata, - } - .abi_encode() - .into(); - (builder.simulator.0.flash_loan_router, calldata) - } - _ => (*builder.simulator.0.settlement.address(), settle_calldata), - }; - - let from = match builder.solver { - Some(Solver::OriginUnaltered(addr)) => addr, - Some(Solver::Fake(opt)) => { - let addr = opt.unwrap_or_else(Address::random); - builder - .account_override_requests - .push(AccountOverrideRequest::SufficientEthBalance(addr)); - builder - .account_override_requests - .push(AccountOverrideRequest::AuthenticateAsSolver(addr)); - addr - } - None => return Err(BuildError::NoSolver), - }; - let state_overrides = build_final_state_overrides( - builder.account_override_requests, - builder.simulator.0.balance_overrides.as_ref(), - builder.simulator.0.authenticator, - ) - .await; - - Ok(EthCallInputs { - request: TransactionRequest { - from: Some(from), - to: Some(to.into()), - input: input.into(), - gas: Some(builder.simulator.0.max_gas_limit), - ..Default::default() - }, - state_overrides, - block, - simulator: builder.simulator, - }) -} - -async fn executed_amount( - builder: &SimulationBuilder, - order: &Order, - block: u64, -) -> Result { - let full = match order.data.kind { - OrderKind::Sell => order.data.sell_amount, - OrderKind::Buy => order.data.buy_amount, - }; - - Ok(match order.executed_amount { - ExecutionAmount::Full => full, - ExecutionAmount::Explicit(amount) => amount, - ExecutionAmount::Remaining => { - let uid = order - .data - .uid(&builder.simulator.0.domain_separator, order.owner); - let filled_amount = builder - .simulator - .0 - .settlement - .filledAmount(Bytes::from(uid.0)) - .block(block.into()) - .call() - .await - .map_err(|err| BuildError::FilledAmountQuery(err.into()))?; - full.saturating_sub(filled_amount) - } - }) -} - -/// Resolves all [`AccountOverrideRequest`]s concurrently on a best-effort -/// basis. Failures are logged and the corresponding override is skipped rather -/// than aborting the whole build. -async fn build_final_state_overrides( - requests: Vec, - balance_overrides: &dyn BalanceOverriding, - authenticator: Address, -) -> StateOverride { - let futures = requests.into_iter().map(|request| async move { - match request { - AccountOverrideRequest::SufficientEthBalance(addr) => Some(( - addr, - AccountOverride::default().with_balance(U256::MAX / U256::from(2)), - )), - AccountOverrideRequest::AuthenticateAsSolver(addr) => { - // GPv2AllowListAuthentication stores `mapping(address => bool) managers` - // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ - // slot_padded). - // - let mut buf = [0u8; 64]; - buf[12..32].copy_from_slice(addr.as_slice()); - buf[32..64].copy_from_slice(&U256::ONE.to_be_bytes::<32>()); - let slot = keccak256(buf); - Some(( - authenticator, - AccountOverride::default() - .with_state_diff(std::iter::once((slot, B256::with_last_byte(1)))), - )) - } - AccountOverrideRequest::Balance { - holder, - token, - amount, - } => { - let result = balance_overrides - .state_override(BalanceOverrideRequest { - token, - holder, - amount, - }) - .await; - if result.is_none() { - tracing::warn!(%token, %holder, "failed to compute balance state override, skipping"); - } - result - } - AccountOverrideRequest::BuyTokensForBuffers => { - tracing::error!("BuyTokensForBuffers is supposed to be replaced with specific balance \ - override requests before assembling the final state overrides"); - None - } - AccountOverrideRequest::Code { account, code } => Some(( - account, - AccountOverride { - code: Some(code), - ..Default::default() - }, - )), - AccountOverrideRequest::Custom { account, state } => Some((account, state)), - } - }); - - let mut state_overrides = StateOverride::default(); - for (address, account_override) in futures::future::join_all(futures) - .await - .into_iter() - .flatten() - { - if let Err(err) = apply_account_override(&mut state_overrides, address, account_override) { - tracing::warn!(?err, %address, "conflicting state overrides for address, skipping"); - } - } - state_overrides -} - -/// Merges `new` into `existing` field by field. -/// -/// Returns [`MergeConflict`] if both overrides write the same field. -/// Non-conflicting `state_diff` entries are combined into a single map. -fn merge_account_override( - existing: &mut AccountOverride, - new: AccountOverride, -) -> Result<(), MergeConflict> { - if new.balance.is_some() { - if existing.balance.is_some() { - return Err(MergeConflict::Balance); - } - existing.balance = new.balance; - } - if new.nonce.is_some() { - if existing.nonce.is_some() { - return Err(MergeConflict::Nonce); - } - existing.nonce = new.nonce; - } - if new.code.is_some() { - if existing.code.is_some() { - return Err(MergeConflict::Code); - } - existing.code = new.code; - } - match (new.state, new.state_diff) { - (Some(new_state), None) => { - if existing.state.is_some() { - return Err(MergeConflict::State); - } - if existing.state_diff.is_some() { - return Err(MergeConflict::StateAndStateDiff); - } - existing.state = Some(new_state); - } - (None, Some(new_diff)) => { - if existing.state.is_some() { - return Err(MergeConflict::StateAndStateDiff); - } - match &mut existing.state_diff { - None => existing.state_diff = Some(new_diff), - Some(existing_diff) => { - for (slot, value) in new_diff { - if existing_diff.contains_key(&slot) { - return Err(MergeConflict::StateDiffSlot(slot)); - } - existing_diff.insert(slot, value); - } - } - } - } - (None, None) => {} - // alloy does not allow both simultaneously, treat as incompatible - (Some(_), Some(_)) => return Err(MergeConflict::StateAndStateDiff), - } - Ok(()) -} - -/// Applies `new` to the override map for `address`. -/// -/// If `address` already has an entry, the overrides are merged via -/// [`merge_account_override`]. Returns an error on conflict. -fn apply_account_override( - overrides: &mut StateOverride, - address: Address, - new: AccountOverride, -) -> Result<(), MergeConflict> { - if let Some(existing) = overrides.get_mut(&address) { - merge_account_override(existing, new) - } else { - overrides.insert(address, new); - Ok(()) - } -} From 7ab386aade4c1f95708be34164300bff071dcc18 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 14:58:45 +0000 Subject: [PATCH 113/154] Make tenderly URL optional --- crates/simulator/src/simulation_builder.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 4272161c9b..79d8c9da69 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -416,14 +416,16 @@ impl EthCallInputs { let tenderly_request = self .to_tenderly_request() .context("failed to convert to tenderly request")?; - let tenderly_url = match &self.simulator.0.tenderly { - Some(api) => Some( - api.simulate_and_share(tenderly_request.clone()) - .await - .context("tenderly failed")?, - ), - None => None, + + let tenderly_url = if let Some(api) = &self.simulator.0.tenderly { + api.simulate_and_share(tenderly_request.clone()) + .await + .inspect_err(|err| tracing::warn!(?err, "failed to simulate via tenderly")) + .ok() + } else { + None }; + let simulation_result = self.simulate().await; Ok(TenderlyReport { From b0348df2e2083885e4c266efbce81bd2ab4a6a87 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 15:00:47 +0000 Subject: [PATCH 114/154] consistent naming --- crates/orderbook/src/orderbook.rs | 4 ++-- crates/price-estimation/src/trade_verifier/mod.rs | 2 +- crates/simulator/src/simulation_builder.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 2a9f51bb82..bcfa9c2c4d 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -639,7 +639,7 @@ impl Orderbook { let sim = order_simulator .new_simulation_builder() - .add_orders([simulation_builder::Order::new(order.data) + .with_orders([simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature) .fill_at( simulation_builder::ExecutionAmount::Remaining, @@ -685,7 +685,7 @@ impl Orderbook { let sim = order_simulator .new_simulation_builder() - .add_orders([simulation_builder::Order::new(OrderData { + .with_orders([simulation_builder::Order::new(OrderData { sell_token: request.sell_token, buy_token: request.buy_token, sell_amount: request.sell_amount.into(), diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index ea4274328e..c9ddf1383a 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -291,7 +291,7 @@ impl TradeVerifier { let eth_call_inputs = self .simulator .new_simulation_builder() - .add_orders(std::iter::once(fake_order).chain(jit_orders)) + .with_orders(std::iter::once(fake_order).chain(jit_orders)) .from_solver(SimulationSolver::OriginUnaltered(solver_address)) .with_pre_interactions(pre_interactions) .with_main_interactions(main_interactions) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 79d8c9da69..38ec95477c 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -138,8 +138,8 @@ pub struct SimulationBuilder { } impl SimulationBuilder { - pub fn add_orders(mut self, orders: impl IntoIterator) -> Self { - self.orders.extend(orders); + pub fn with_orders(mut self, orders: impl IntoIterator) -> Self { + self.orders = orders.into_iter().collect(); self } From d299a38ebf0f7a40b9e706dbd2be3e1bc7443671 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 15:21:38 +0000 Subject: [PATCH 115/154] Replace awkward BuyTokensForBuffers variant --- crates/orderbook/src/orderbook.rs | 4 ++-- crates/simulator/src/encoding.rs | 17 +---------------- crates/simulator/src/simulation_builder.rs | 14 +++++++++++--- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index bcfa9c2c4d..266c2c4449 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -654,7 +654,7 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_overrides([simulation_builder::AccountOverrideRequest::BuyTokensForBuffers]) + .provide_sufficient_buy_tokens() .from_solver(simulation_builder::Solver::Fake(None)) .build() .await @@ -717,7 +717,7 @@ impl Orderbook { .map(simulation_builder::Block::Number) .unwrap_or(simulation_builder::Block::Latest), ) - .with_overrides([simulation_builder::AccountOverrideRequest::BuyTokensForBuffers]) + .provide_sufficient_buy_tokens() .from_solver(simulation_builder::Solver::Fake(None)) .build() .await diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index aa2eef46cf..1d897f707c 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -385,17 +385,7 @@ pub(crate) async fn finish_simulation_builder( clearing_prices.push(buy_price); } - // Expand any BuyTokensForBuffers request into one Balance override per - // order, then remove all BuyTokensForBuffers entries so duplicates are - // impossible. - if builder - .account_override_requests - .iter() - .any(|r| matches!(r, AccountOverrideRequest::BuyTokensForBuffers)) - { - builder - .account_override_requests - .retain(|r| !matches!(r, AccountOverrideRequest::BuyTokensForBuffers)); + if builder.provide_buy_tokens { let settlement = *builder.simulator.0.settlement.address(); for (i, (order, &exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { let sell_price = clearing_prices[2 * i]; @@ -594,11 +584,6 @@ async fn build_final_state_overrides( } result } - AccountOverrideRequest::BuyTokensForBuffers => { - tracing::error!("BuyTokensForBuffers is supposed to be replaced with specific balance \ - override requests before assembling the final state overrides"); - None - } AccountOverrideRequest::Code { account, code } => Some(( account, AccountOverride { diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 38ec95477c..9941b5f9b1 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -104,6 +104,7 @@ impl SettlementSimulator { solver: None, auction_id: None, account_override_requests: vec![], + provide_buy_tokens: false, block: Block::Latest, } } @@ -134,6 +135,7 @@ pub struct SimulationBuilder { pub(crate) auction_id: Option, pub(crate) simulator: SettlementSimulator, pub(crate) account_override_requests: Vec, + pub(crate) provide_buy_tokens: bool, pub(crate) block: Block, } @@ -244,6 +246,15 @@ impl SimulationBuilder { Ok(self) } + /// Instructs the builder to override the settlement contract's buy-token + /// balances so it can pay out every order. The exact amounts are derived + /// from the clearing prices and executed amounts once + /// [`build`](Self::build) is called. + pub fn provide_sufficient_buy_tokens(mut self) -> Self { + self.provide_buy_tokens = true; + self + } + /// Queues [`AccountOverrideRequest`]s to be resolved and applied during /// [`build`](Self::build). Multiple requests may target the same address /// and will be applied on a best-effort basis (failure to compute balance @@ -518,9 +529,6 @@ pub enum AccountOverrideRequest { token: Address, amount: U256, }, - /// Gives the settlement contract enough buy tokens to pay for all - /// orders. - BuyTokensForBuffers, /// Deploys the provided code at the requested address. Code { account: Address, code: Bytes }, /// Allows to build fully custom overrides for the most exotic use cases. From 7d4c4aa39228177ab395982d4714ed1ec2a0838f Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 15:26:39 +0000 Subject: [PATCH 116/154] Stricter error handling --- crates/orderbook/src/orderbook.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 266c2c4449..9f0d8c29f1 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -658,12 +658,14 @@ impl Orderbook { .from_solver(simulation_builder::Solver::Fake(None)) .build() .await - .context("failed to finalize simulation")?; + .context("failed to finalize simulation") + .map_err(OrderSimulationError::Other)?; let simulation_result = sim .simulate_with_tenderly_report() .await - .context("failed to execute simulation")?; + .context("failed to execute simulation") + .map_err(OrderSimulationError::Other)?; Ok(Some(OrderSimulationResult { tenderly_url: simulation_result.tenderly_url, @@ -760,7 +762,7 @@ pub enum OrderSimulationError { #[error("order simulation is not enabled")] NotEnabled, #[error("malformed input")] - MalformedInput(#[from] anyhow::Error), + MalformedInput(anyhow::Error), #[error("simulation could not be created for order")] Other(anyhow::Error), } From ea48bce90a9c8169766c5b3eb30fe5d7474b5c0d Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 15:39:26 +0000 Subject: [PATCH 117/154] move code around + comments --- .../price-estimation/src/trade_verifier/mod.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index c9ddf1383a..eb12ee6466 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -171,7 +171,7 @@ impl TradeVerifier { verification.receiver }; - // storeBalance interactions bracket the settlement to measure the actual + // storeBalance interactions surrounding the settlement to measure the actual // out_amount let (tracked_token, tracked_owner) = match query.kind { OrderKind::Sell => (query.buy_token, effective_receiver), @@ -188,13 +188,6 @@ impl TradeVerifier { .abi_encode(), }; - // WETH unwrap so ETH buy orders can pay out native tokens - let weth_unwrap = (query.buy_token == BUY_ETH_ADDRESS).then(|| InteractionData { - target: self.simulator.native_token(), - value: U256::ZERO, - call_data: WETH9::WETH9::withdrawCall { wad: buy_amount }.abi_encode(), - }); - // pre: [verification.pre, trade.pre, trade_setup, storeBalance_before] let pre_interactions: Vec = map_interactions_data( verification @@ -207,6 +200,12 @@ impl TradeVerifier { .chain([store_balance.clone()]) .collect(); + // WETH unwrap so ETH buy orders can pay out native tokens + let weth_unwrap = (query.buy_token == BUY_ETH_ADDRESS).then(|| InteractionData { + target: self.simulator.native_token(), + value: U256::ZERO, + call_data: WETH9::WETH9::withdrawCall { wad: buy_amount }.abi_encode(), + }); // main: [trade.main, weth_unwrap] let main_interactions: Vec = map_interactions_data(trade.interactions()) .into_iter() @@ -301,6 +300,8 @@ impl TradeVerifier { .await .map_err(|e| Error::SimulationFailed(anyhow::anyhow!("{e}")))?; + // after assembling the state overrides and settlement call data we need to + // craft a call that takes the settle call data as an argument. let settlement_target = eth_call_inputs .request .to From a4aa3bf47b5572b8c49950e71d5597bbf2a3d855 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Tue, 5 May 2026 15:45:56 +0000 Subject: [PATCH 118/154] Revert native token handling change to shrink diff --- contracts/artifacts/Solver.json | 9 +++- contracts/artifacts/Trader.json | 9 +++- .../contracts-generated/solver/src/lib.rs | 41 +++++++++++++------ .../contracts-generated/trader/src/lib.rs | 41 +++++++++++++------ contracts/solidity/Solver.sol | 4 +- contracts/solidity/Trader.sol | 19 +++++++++ .../src/trade_verifier/mod.rs | 1 + 7 files changed, 95 insertions(+), 29 deletions(-) diff --git a/contracts/artifacts/Solver.json b/contracts/artifacts/Solver.json index 1a0068b09a..94ba3bd7da 100644 --- a/contracts/artifacts/Solver.json +++ b/contracts/artifacts/Solver.json @@ -22,6 +22,11 @@ "name": "sellAmount", "type": "uint256" }, + { + "internalType": "address", + "name": "nativeToken", + "type": "address" + }, { "internalType": "address", "name": "spardose", @@ -96,8 +101,8 @@ "type": "function" } ], - "bytecode": "0x6080604052348015600e575f5ffd5b506109ab8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a", - "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a", + "bytecode": "0x6080604052348015600e575f5ffd5b506109c58061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a", "devdoc": { "methods": {} }, diff --git a/contracts/artifacts/Trader.json b/contracts/artifacts/Trader.json index 7a9cb937ef..2bb2245e43 100644 --- a/contracts/artifacts/Trader.json +++ b/contracts/artifacts/Trader.json @@ -21,6 +21,11 @@ "name": "sellAmount", "type": "uint256" }, + { + "internalType": "address", + "name": "nativeToken", + "type": "address" + }, { "internalType": "address", "name": "spardose", @@ -84,8 +89,8 @@ "type": "receive" } ], - "bytecode": "0x6080604052348015600e575f5ffd5b50610baa8061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a", - "deployedBytecode": "0x608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a", + "bytecode": "0x6080604052348015600e575f5ffd5b50610d008061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a", + "deployedBytecode": "0x608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a", "devdoc": { "methods": {} }, diff --git a/contracts/generated/contracts-generated/solver/src/lib.rs b/contracts/generated/contracts-generated/solver/src/lib.rs index fbfe9aad21..cd27b738be 100644 --- a/contracts/generated/contracts-generated/solver/src/lib.rs +++ b/contracts/generated/contracts-generated/solver/src/lib.rs @@ -11,7 +11,7 @@ Generated by the following Solidity interface... ```solidity interface Solver { - function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; + function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; function storeBalance(address token, address owner, bool countGas) external; function swap(address settlementContract, address[] memory tokens, address payable receiver, bytes memory settlementCall) external returns (uint256 gasUsed, uint256[] memory queriedBalances); } @@ -44,6 +44,11 @@ interface Solver { "type": "uint256", "internalType": "uint256" }, + { + "name": "nativeToken", + "type": "address", + "internalType": "address" + }, { "name": "spardose", "type": "address", @@ -129,27 +134,27 @@ pub mod Solver { /// The creation / init bytecode of the contract. /// /// ```text - ///0x6080604052348015600e575f5ffd5b506109ab8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a + ///0x6080604052348015600e575f5ffd5b506109c58061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\t\xAB\x80a\0\x1C_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c/\xF1\x11\xF5\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x06\xF9V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xBDV[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\nV[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08nV[a\x03\x15V[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x84V[a\x01\xA1\x88\x85\x85a\x05NV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x84V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7F[\xBE\x8B?\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x81\x16`\x04\x83\x01R\x86\x81\x16`$\x83\x01R`D\x82\x01\x86\x90R\x84\x81\x16`d\x83\x01R\x91\x92P\x90\x87\x16\x90c[\xBE\x8B?\x90`\x84\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xACW__\xFD[PZ\xF1\x15\x80\x15a\x02\xBEW=__>=_\xFD[PPPPZa\x02\xCD\x90\x82a\x08\xE7V[a\x02\xD9\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x08\x91\x90a\t\0V[\x90\x91UPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x03\xFEW`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xD5W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xF9\x91\x90a\t\x13V[a\x04\x17V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04~WZa\x04=\x90\x82a\x08\xE7V[a\x04I\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04x\x91\x90a\t\0V[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04~W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xA6Wa\x04\xA6a\t*V[\x90P` \x02\x01` \x81\x01\x90a\x04\xBB\x91\x90a\tWV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05,W__\xFD[PZ\xF1\x15\x80\x15a\x05>W=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x86\x90PV[__Z\x90Pa\x05\xA9\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xEAV[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xD7\x90\x83a\x08\xE7V[a\x05\xE1\x91\x90a\x08\xE7V[\x95\x94PPPPPV[``a\x05\xF7\x83_\x84a\x05\xFEV[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x06'\x91\x90a\trV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06aW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06fV[``\x91P[P\x92P\x90P\x80a\x06xW\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xA1W__\xFD[PV[\x805a\x06\xAF\x81a\x06\x80V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xC4W__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xDBW__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xF2W__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x0EW__\xFD[\x865a\x07\x19\x81a\x06\x80V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x074W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07DW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07ZW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07nW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x84`@\x88\x01a\x06\xA4V[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9FW__\xFD[a\x07\xAB\x89\x82\x8A\x01a\x06\xB4V[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x07\xFEW\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE0V[P\x90\x96\x95PPPPPPV[_____`\xA0\x86\x88\x03\x12\x15a\x08\x1EW__\xFD[\x855a\x08)\x81a\x06\x80V[\x94P` \x86\x015a\x089\x81a\x06\x80V[\x93P`@\x86\x015a\x08I\x81a\x06\x80V[\x92P``\x86\x015\x91P`\x80\x86\x015a\x08`\x81a\x06\x80V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x08\x80W__\xFD[\x835a\x08\x8B\x81a\x06\x80V[\x92P` \x84\x015a\x08\x9B\x81a\x06\x80V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xAFW__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[_` \x82\x84\x03\x12\x15a\t#W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\tgW__\xFD[\x815a\x05\xF7\x81a\x06\x80V[_\x82Q_[\x81\x81\x10\x15a\t\x91W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\twV[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\t\xC5\x80a\0\x1C_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c%\x82\xED\xB4\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x07\x02V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xC6V[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\x13V[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08\x88V[a\x03\x1EV[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x8DV[a\x01\xA1\x88\x85\x85a\x05WV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x8DV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7FT.\xB7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x81\x16`\x04\x83\x01R\x87\x81\x16`$\x83\x01R`D\x82\x01\x87\x90R\x85\x81\x16`d\x83\x01R\x84\x81\x16`\x84\x83\x01R\x91\x92P\x90\x88\x16\x90cT.\xB7}\x90`\xA4\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xB4W__\xFD[PZ\xF1\x15\x80\x15a\x02\xC6W=__>=_\xFD[PPPPZa\x02\xD5\x90\x82a\t\x01V[a\x02\xE1\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x10\x91\x90a\t\x1AV[\x90\x91UPPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x04\x07W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xDEW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\x02\x91\x90a\t-V[a\x04 V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04\x87WZa\x04F\x90\x82a\t\x01V[a\x04R\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04\x81\x91\x90a\t\x1AV[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04\x87W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xAFWa\x04\xAFa\tDV[\x90P` \x02\x01` \x81\x01\x90a\x04\xC4\x91\x90a\tqV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x055W__\xFD[PZ\xF1\x15\x80\x15a\x05GW=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x8F\x90PV[__Z\x90Pa\x05\xB2\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xF3V[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xE0\x90\x83a\t\x01V[a\x05\xEA\x91\x90a\t\x01V[\x95\x94PPPPPV[``a\x06\0\x83_\x84a\x06\x07V[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x060\x91\x90a\t\x8CV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06jW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06oV[``\x91P[P\x92P\x90P\x80a\x06\x81W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xAAW__\xFD[PV[\x805a\x06\xB8\x81a\x06\x89V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xCDW__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xE4W__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xFBW__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x17W__\xFD[\x865a\x07\"\x81a\x06\x89V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07=W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07MW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07cW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07wW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x8D`@\x88\x01a\x06\xADV[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\xA8W__\xFD[a\x07\xB4\x89\x82\x8A\x01a\x06\xBDV[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x08\x07W\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE9V[P\x90\x96\x95PPPPPPV[______`\xC0\x87\x89\x03\x12\x15a\x08(W__\xFD[\x865a\x083\x81a\x06\x89V[\x95P` \x87\x015a\x08C\x81a\x06\x89V[\x94P`@\x87\x015a\x08S\x81a\x06\x89V[\x93P``\x87\x015\x92P`\x80\x87\x015a\x08j\x81a\x06\x89V[\x91P`\xA0\x87\x015a\x08z\x81a\x06\x89V[\x80\x91PP\x92\x95P\x92\x95P\x92\x95V[___``\x84\x86\x03\x12\x15a\x08\x9AW__\xFD[\x835a\x08\xA5\x81a\x06\x89V[\x92P` \x84\x015a\x08\xB5\x81a\x06\x89V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xC9W__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[_` \x82\x84\x03\x12\x15a\t=W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\t\x81W__\xFD[\x815a\x06\0\x81a\x06\x89V[_\x82Q_[\x81\x81\x10\x15a\t\xABW` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\t\x91V[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", ); /// The runtime bytecode of the contract, as deployed on the network. /// /// ```text - ///0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632ff111f51461006d5780633bbb2e1d14610082575b5f5ffd5b6100566100513660046106f9565b610095565b6040516100649291906107bd565b60405180910390f35b61008061007b36600461080a565b610229565b005b61008061009036600461086e565b610315565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a610484565b6101a188858561054e565b91506101ae87878a610484565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f5bbe8b3f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152868116602483015260448201869052848116606483015291925090871690635bbe8b3f906084015f604051808303815f87803b1580156102ac575f5ffd5b505af11580156102be573d5f5f3e3d5ffd5b505050505a6102cd90826108e7565b6102d99061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546103089190610900565b9091555050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103fe576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103d5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103f99190610913565b610417565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f928352602090922090910155811561047e575a61043d90826108e7565b6104499061116c610900565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f8282546104789190610900565b90915550505b50505050565b5f5b8281101561047e5730633bbb2e1d8585848181106104a6576104a661092a565b90506020020160208101906104bb9190610957565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b15801561052c575f5ffd5b505af115801561053e573d5f5f3e3d5ffd5b5050600190920191506104869050565b5f5f5a90506105a984848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105ea565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105d790836108e7565b6105e191906108e7565b95945050505050565b60606105f7835f846105fe565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516106279190610972565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50925090508061067857815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106a1575f5ffd5b50565b80356106af81610680565b919050565b5f5f83601f8401126106c4575f5ffd5b50813567ffffffffffffffff8111156106db575f5ffd5b6020830191508360208285010111156106f2575f5ffd5b9250929050565b5f5f5f5f5f5f6080878903121561070e575f5ffd5b863561071981610680565b9550602087013567ffffffffffffffff811115610734575f5ffd5b8701601f81018913610744575f5ffd5b803567ffffffffffffffff81111561075a575f5ffd5b8960208260051b840101111561076e575f5ffd5b60209190910195509350610784604088016106a4565b9250606087013567ffffffffffffffff81111561079f575f5ffd5b6107ab89828a016106b4565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156107fe5783518352602093840193909201916001016107e0565b50909695505050505050565b5f5f5f5f5f60a0868803121561081e575f5ffd5b853561082981610680565b9450602086013561083981610680565b9350604086013561084981610680565b925060608601359150608086013561086081610680565b809150509295509295909350565b5f5f5f60608486031215610880575f5ffd5b833561088b81610680565b9250602084013561089b81610680565b9150604084013580151581146108af575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156108fa576108fa6108ba565b92915050565b808201808211156108fa576108fa6108ba565b5f60208284031215610923575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610967575f5ffd5b81356105f781610680565b5f82515f5b818110156109915760208186018101518583015201610977565b505f92019182525091905056fea164736f6c634300081e000a + ///0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80631d47e7f4146100435780632582edb41461006d5780633bbb2e1d14610082575b5f5ffd5b610056610051366004610702565b610095565b6040516100649291906107c6565b60405180910390f35b61008061007b366004610813565b610229565b005b610080610090366004610888565b61031e565b5f606033301461012b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff165f6040515f6040518083038185875af1925050503d805f8114610181576040519150601f19603f3d011682016040523d82523d5f602084013e610186565b606091505b505090505061019687878a61048d565b6101a1888585610557565b91506101ae87878a61048d565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c80548060200260200160405190810160405280929190818152602001828054801561021757602002820191905f5260205f20905b815481526020019060010190808311610203575b50505050509050965096945050505050565b5f5a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a4015f604051808303815f87803b1580156102b4575f5ffd5b505af11580156102c6573d5f5f3e3d5ffd5b505050505a6102d59082610901565b6102e19061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610310919061091a565b909155505050505050505050565b5f5a90507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722c73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610407576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa1580156103de573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610402919061092d565b610420565b8373ffffffffffffffffffffffffffffffffffffffff16315b81546001810183555f9283526020909220909101558115610487575a6104469082610901565b6104529061116c61091a565b7f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b5f828254610481919061091a565b90915550505b50505050565b5f5b828110156104875730633bbb2e1d8585848181106104af576104af610944565b90506020020160208101906104c49190610971565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff918216600482015290851660248201525f60448201526064015f604051808303815f87803b158015610535575f5ffd5b505af1158015610547573d5f5f3e3d5ffd5b50506001909201915061048f9050565b5f5f5a90506105b284848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505073ffffffffffffffffffffffffffffffffffffffff8916929150506105f3565b507f14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff18c9a173a722b545a6105e09083610901565b6105ea9190610901565b95945050505050565b6060610600835f84610607565b9392505050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610630919061098c565b5f6040518083038185875af1925050503d805f811461066a576040519150601f19603f3d011682016040523d82523d5f602084013e61066f565b606091505b50925090508061068157815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146106aa575f5ffd5b50565b80356106b881610689565b919050565b5f5f83601f8401126106cd575f5ffd5b50813567ffffffffffffffff8111156106e4575f5ffd5b6020830191508360208285010111156106fb575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610717575f5ffd5b863561072281610689565b9550602087013567ffffffffffffffff81111561073d575f5ffd5b8701601f8101891361074d575f5ffd5b803567ffffffffffffffff811115610763575f5ffd5b8960208260051b8401011115610777575f5ffd5b6020919091019550935061078d604088016106ad565b9250606087013567ffffffffffffffff8111156107a8575f5ffd5b6107b489828a016106bd565b979a9699509497509295939492505050565b5f60408201848352604060208401528084518083526060850191506020860192505f5b818110156108075783518352602093840193909201916001016107e9565b50909695505050505050565b5f5f5f5f5f5f60c08789031215610828575f5ffd5b863561083381610689565b9550602087013561084381610689565b9450604087013561085381610689565b935060608701359250608087013561086a81610689565b915060a087013561087a81610689565b809150509295509295509295565b5f5f5f6060848603121561089a575f5ffd5b83356108a581610689565b925060208401356108b581610689565b9150604084013580151581146108c9575f5ffd5b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115610914576109146108d4565b92915050565b80820180821115610914576109146108d4565b5f6020828403121561093d575f5ffd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f60208284031215610981575f5ffd5b813561060081610689565b5f82515f5b818110156109ab5760208186018101518583015201610991565b505f92019182525091905056fea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c/\xF1\x11\xF5\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x06\xF9V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xBDV[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\nV[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08nV[a\x03\x15V[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x84V[a\x01\xA1\x88\x85\x85a\x05NV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x84V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7F[\xBE\x8B?\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x81\x16`\x04\x83\x01R\x86\x81\x16`$\x83\x01R`D\x82\x01\x86\x90R\x84\x81\x16`d\x83\x01R\x91\x92P\x90\x87\x16\x90c[\xBE\x8B?\x90`\x84\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xACW__\xFD[PZ\xF1\x15\x80\x15a\x02\xBEW=__>=_\xFD[PPPPZa\x02\xCD\x90\x82a\x08\xE7V[a\x02\xD9\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x08\x91\x90a\t\0V[\x90\x91UPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x03\xFEW`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xD5W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xF9\x91\x90a\t\x13V[a\x04\x17V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04~WZa\x04=\x90\x82a\x08\xE7V[a\x04I\x90a\x11la\t\0V[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04x\x91\x90a\t\0V[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04~W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xA6Wa\x04\xA6a\t*V[\x90P` \x02\x01` \x81\x01\x90a\x04\xBB\x91\x90a\tWV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05,W__\xFD[PZ\xF1\x15\x80\x15a\x05>W=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x86\x90PV[__Z\x90Pa\x05\xA9\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xEAV[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xD7\x90\x83a\x08\xE7V[a\x05\xE1\x91\x90a\x08\xE7V[\x95\x94PPPPPV[``a\x05\xF7\x83_\x84a\x05\xFEV[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x06'\x91\x90a\trV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06aW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06fV[``\x91P[P\x92P\x90P\x80a\x06xW\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xA1W__\xFD[PV[\x805a\x06\xAF\x81a\x06\x80V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xC4W__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xDBW__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xF2W__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x0EW__\xFD[\x865a\x07\x19\x81a\x06\x80V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x074W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07DW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07ZW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07nW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x84`@\x88\x01a\x06\xA4V[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9FW__\xFD[a\x07\xAB\x89\x82\x8A\x01a\x06\xB4V[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x07\xFEW\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE0V[P\x90\x96\x95PPPPPPV[_____`\xA0\x86\x88\x03\x12\x15a\x08\x1EW__\xFD[\x855a\x08)\x81a\x06\x80V[\x94P` \x86\x015a\x089\x81a\x06\x80V[\x93P`@\x86\x015a\x08I\x81a\x06\x80V[\x92P``\x86\x015\x91P`\x80\x86\x015a\x08`\x81a\x06\x80V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x08\x80W__\xFD[\x835a\x08\x8B\x81a\x06\x80V[\x92P` \x84\x015a\x08\x9B\x81a\x06\x80V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xAFW__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\x08\xFAWa\x08\xFAa\x08\xBAV[_` \x82\x84\x03\x12\x15a\t#W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\tgW__\xFD[\x815a\x05\xF7\x81a\x06\x80V[_\x82Q_[\x81\x81\x10\x15a\t\x91W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\twV[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15a\0\x0FW__\xFD[P`\x046\x10a\0?W_5`\xE0\x1C\x80c\x1DG\xE7\xF4\x14a\0CW\x80c%\x82\xED\xB4\x14a\0mW\x80c;\xBB.\x1D\x14a\0\x82W[__\xFD[a\0Va\0Q6`\x04a\x07\x02V[a\0\x95V[`@Qa\0d\x92\x91\x90a\x07\xC6V[`@Q\x80\x91\x03\x90\xF3[a\0\x80a\0{6`\x04a\x08\x13V[a\x02)V[\0[a\0\x80a\0\x906`\x04a\x08\x88V[a\x03\x1EV[_``30\x14a\x01+W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`8`$\x82\x01R\x7Fonly simulation logic is allowed`D\x82\x01R\x7F to call 'swap' function\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01`@Q\x80\x91\x03\x90\xFD[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16_`@Q_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x01\x81W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\x86V[``\x91P[PP\x90PPa\x01\x96\x87\x87\x8Aa\x04\x8DV[a\x01\xA1\x88\x85\x85a\x05WV[\x91Pa\x01\xAE\x87\x87\x8Aa\x04\x8DV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,\x80T\x80` \x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80T\x80\x15a\x02\x17W` \x02\x82\x01\x91\x90_R` _ \x90[\x81T\x81R` \x01\x90`\x01\x01\x90\x80\x83\x11a\x02\x03W[PPPPP\x90P\x96P\x96\x94PPPPPV[_Z`@Q\x7FT.\xB7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x81\x16`\x04\x83\x01R\x87\x81\x16`$\x83\x01R`D\x82\x01\x87\x90R\x85\x81\x16`d\x83\x01R\x84\x81\x16`\x84\x83\x01R\x91\x92P\x90\x88\x16\x90cT.\xB7}\x90`\xA4\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x02\xB4W__\xFD[PZ\xF1\x15\x80\x15a\x02\xC6W=__>=_\xFD[PPPPZa\x02\xD5\x90\x82a\t\x01V[a\x02\xE1\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x03\x10\x91\x90a\t\x1AV[\x90\x91UPPPPPPPPPV[_Z\x90P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r,s\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEEs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x14a\x04\x07W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x85\x81\x16`\x04\x83\x01R\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\xDEW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\x02\x91\x90a\t-V[a\x04 V[\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x161[\x81T`\x01\x81\x01\x83U_\x92\x83R` \x90\x92 \x90\x91\x01U\x81\x15a\x04\x87WZa\x04F\x90\x82a\t\x01V[a\x04R\x90a\x11la\t\x1AV[\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+_\x82\x82Ta\x04\x81\x91\x90a\t\x1AV[\x90\x91UPP[PPPPV[_[\x82\x81\x10\x15a\x04\x87W0c;\xBB.\x1D\x85\x85\x84\x81\x81\x10a\x04\xAFWa\x04\xAFa\tDV[\x90P` \x02\x01` \x81\x01\x90a\x04\xC4\x91\x90a\tqV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x84\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x91\x82\x16`\x04\x82\x01R\x90\x85\x16`$\x82\x01R_`D\x82\x01R`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x055W__\xFD[PZ\xF1\x15\x80\x15a\x05GW=__>=_\xFD[PP`\x01\x90\x92\x01\x91Pa\x04\x8F\x90PV[__Z\x90Pa\x05\xB2\x84\x84\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPPs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x89\x16\x92\x91PPa\x05\xF3V[P\x7F\x14\xF5\xB2\xC1\x85\xFC\x03\xC7\\x}\x1F\x0E\x10\xEA\x13|\xC6\xD25\xA0\x04tH\xEF\xF1\x8C\x9A\x17:r+TZa\x05\xE0\x90\x83a\t\x01V[a\x05\xEA\x91\x90a\t\x01V[\x95\x94PPPPPV[``a\x06\0\x83_\x84a\x06\x07V[\x93\x92PPPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\x060\x91\x90a\t\x8CV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\x06jW`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x06oV[``\x91P[P\x92P\x90P\x80a\x06\x81W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x06\xAAW__\xFD[PV[\x805a\x06\xB8\x81a\x06\x89V[\x91\x90PV[__\x83`\x1F\x84\x01\x12a\x06\xCDW__\xFD[P\x815g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x06\xE4W__\xFD[` \x83\x01\x91P\x83` \x82\x85\x01\x01\x11\x15a\x06\xFBW__\xFD[\x92P\x92\x90PV[______`\x80\x87\x89\x03\x12\x15a\x07\x17W__\xFD[\x865a\x07\"\x81a\x06\x89V[\x95P` \x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07=W__\xFD[\x87\x01`\x1F\x81\x01\x89\x13a\x07MW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07cW__\xFD[\x89` \x82`\x05\x1B\x84\x01\x01\x11\x15a\x07wW__\xFD[` \x91\x90\x91\x01\x95P\x93Pa\x07\x8D`@\x88\x01a\x06\xADV[\x92P``\x87\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\xA8W__\xFD[a\x07\xB4\x89\x82\x8A\x01a\x06\xBDV[\x97\x9A\x96\x99P\x94\x97P\x92\x95\x93\x94\x92PPPV[_`@\x82\x01\x84\x83R`@` \x84\x01R\x80\x84Q\x80\x83R``\x85\x01\x91P` \x86\x01\x92P_[\x81\x81\x10\x15a\x08\x07W\x83Q\x83R` \x93\x84\x01\x93\x90\x92\x01\x91`\x01\x01a\x07\xE9V[P\x90\x96\x95PPPPPPV[______`\xC0\x87\x89\x03\x12\x15a\x08(W__\xFD[\x865a\x083\x81a\x06\x89V[\x95P` \x87\x015a\x08C\x81a\x06\x89V[\x94P`@\x87\x015a\x08S\x81a\x06\x89V[\x93P``\x87\x015\x92P`\x80\x87\x015a\x08j\x81a\x06\x89V[\x91P`\xA0\x87\x015a\x08z\x81a\x06\x89V[\x80\x91PP\x92\x95P\x92\x95P\x92\x95V[___``\x84\x86\x03\x12\x15a\x08\x9AW__\xFD[\x835a\x08\xA5\x81a\x06\x89V[\x92P` \x84\x015a\x08\xB5\x81a\x06\x89V[\x91P`@\x84\x015\x80\x15\x15\x81\x14a\x08\xC9W__\xFD[\x80\x91PP\x92P\x92P\x92V[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[\x81\x81\x03\x81\x81\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[\x92\x91PPV[\x80\x82\x01\x80\x82\x11\x15a\t\x14Wa\t\x14a\x08\xD4V[_` \x82\x84\x03\x12\x15a\t=W__\xFD[PQ\x91\x90PV[\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`2`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\t\x81W__\xFD[\x815a\x06\0\x81a\x06\x89V[_\x82Q_[\x81\x81\x10\x15a\t\xABW` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\t\x91V[P_\x92\x01\x91\x82RP\x91\x90PV\xFE\xA1dsolcC\0\x08\x1E\0\n", ); #[derive(Default, Debug, PartialEq, Eq, Hash)] - /**Function with signature `ensureTradePreconditions(address,address,address,uint256,address)` and selector `0x2ff111f5`. + /**Function with signature `ensureTradePreconditions(address,address,address,uint256,address,address)` and selector `0x2582edb4`. ```solidity - function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; + function ensureTradePreconditions(address trader, address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; ```*/ #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -163,10 +168,12 @@ pub mod Solver { #[allow(missing_docs)] pub sellAmount: alloy_sol_types::private::primitives::aliases::U256, #[allow(missing_docs)] + pub nativeToken: alloy_sol_types::private::Address, + #[allow(missing_docs)] pub spardose: alloy_sol_types::private::Address, } ///Container type for the return parameters of the - /// [`ensureTradePreconditions(address,address,address,uint256, + /// [`ensureTradePreconditions(address,address,address,uint256,address, /// address)`](ensureTradePreconditionsCall) function. #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -188,6 +195,7 @@ pub mod Solver { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, + alloy_sol_types::sol_data::Address, ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( @@ -196,6 +204,7 @@ pub mod Solver { alloy_sol_types::private::Address, alloy_sol_types::private::primitives::aliases::U256, alloy_sol_types::private::Address, + alloy_sol_types::private::Address, ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] @@ -215,6 +224,7 @@ pub mod Solver { value.settlementContract, value.sellToken, value.sellAmount, + value.nativeToken, value.spardose, ) } @@ -228,7 +238,8 @@ pub mod Solver { settlementContract: tuple.1, sellToken: tuple.2, sellAmount: tuple.3, - spardose: tuple.4, + nativeToken: tuple.4, + spardose: tuple.5, } } } @@ -279,15 +290,16 @@ pub mod Solver { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, + alloy_sol_types::sol_data::Address, ); type Return = ensureTradePreconditionsReturn; type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; type ReturnTuple<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SELECTOR: [u8; 4] = [47u8, 241u8, 17u8, 245u8]; + const SELECTOR: [u8; 4] = [37u8, 130u8, 237u8, 180u8]; const SIGNATURE: &'static str = - "ensureTradePreconditions(address,address,address,uint256,address)"; + "ensureTradePreconditions(address,address,address,uint256,address,address)"; #[inline] fn new<'a>( @@ -311,6 +323,9 @@ pub mod Solver { as alloy_sol_types::SolType>::tokenize( &self.sellAmount, ), + ::tokenize( + &self.nativeToken, + ), ::tokenize( &self.spardose, ), @@ -721,7 +736,7 @@ pub mod Solver { /// Prefer using `SolInterface` methods instead. pub const SELECTORS: &'static [[u8; 4usize]] = &[ [29u8, 71u8, 231u8, 244u8], - [47u8, 241u8, 17u8, 245u8], + [37u8, 130u8, 237u8, 180u8], [59u8, 187u8, 46u8, 29u8], ]; /// The signatures in the same order as `SELECTORS`. @@ -1068,6 +1083,7 @@ pub mod Solver { settlementContract: alloy_sol_types::private::Address, sellToken: alloy_sol_types::private::Address, sellAmount: alloy_sol_types::private::primitives::aliases::U256, + nativeToken: alloy_sol_types::private::Address, spardose: alloy_sol_types::private::Address, ) -> alloy_contract::SolCallBuilder<&P, ensureTradePreconditionsCall, N> { self.call_builder(&ensureTradePreconditionsCall { @@ -1075,6 +1091,7 @@ pub mod Solver { settlementContract, sellToken, sellAmount, + nativeToken, spardose, }) } diff --git a/contracts/generated/contracts-generated/trader/src/lib.rs b/contracts/generated/contracts-generated/trader/src/lib.rs index 8fb9497e99..9583bc614e 100644 --- a/contracts/generated/contracts-generated/trader/src/lib.rs +++ b/contracts/generated/contracts-generated/trader/src/lib.rs @@ -15,7 +15,7 @@ interface Trader { receive() external payable; - function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; + function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; function isValidSignature(bytes32, bytes memory) external pure returns (bytes4); function safeApprove(address token, address vaultRelayer, uint256 amount) external; } @@ -51,6 +51,11 @@ interface Trader { "type": "uint256", "internalType": "uint256" }, + { + "name": "nativeToken", + "type": "address", + "internalType": "address" + }, { "name": "spardose", "type": "address", @@ -121,27 +126,27 @@ pub mod Trader { /// The creation / init bytecode of the contract. /// /// ```text - ///0x6080604052348015600e575f5ffd5b50610baa8061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a + ///0x6080604052348015600e575f5ffd5b50610d008061001c5f395ff3fe608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\x0B\xAA\x80a\0\x1C_9_\xF3\xFE`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80c[\xBE\x8B?\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\t\xBFV[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\nZV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\n\xAAV[a\x07\xD4V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x02\xEDW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\x11\x91\x90a\x0B\x14V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x86\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\x86W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xAA\x91\x90a\x0B/V[\x90P\x83\x81\x10\x15a\x06\x07W`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04&W__\xFD[PZ\xF1\x92PPP\x80\x15a\x047WP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04\xCAW__\xFD[PZ\xF1\x92PPP\x80\x15a\x04\xDBWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x05OW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x05s\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x06\x05W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06qW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\x95\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x07\xCBWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x87a\x06\xC6\x84\x89a\x0BFV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x07.W__\xFD[PZ\xF1\x92PPP\x80\x15a\x07?WP`\x01[a\x07\xCBW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPV[a\x07\xF5s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\x07\xFAV[PPPV[`@\x80Qs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x81\x16`$\x83\x01R`D\x80\x83\x01\x85\x90R\x83Q\x80\x84\x03\x90\x91\x01\x81R`d\x90\x92\x01\x90\x92R` \x81\x01\x80Q{\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x7F\t^\xA7\xB3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x17\x90R\x90_\x90a\x08\x8C\x90\x86\x16\x83a\t\x04V[\x90Pa\x08\x97\x81a\t\x18V[a\x08\xFDW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`\x1A`$\x82\x01R\x7FSafeERC20: approval failed\0\0\0\0\0\0`D\x82\x01R`d\x01a\x02\x9AV[PPPPPV[``a\t\x11\x83_\x84a\t=V[\x93\x92PPPV[_\x81Q_\x14\x80a\t7WP\x81\x80` \x01\x90Q\x81\x01\x90a\t7\x91\x90a\x0B~V[\x92\x91PPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\tf\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\t\xA0W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\t\xA5V[``\x91P[P\x92P\x90P\x80a\t\xB7W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\t\xD1W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\t\xEEW__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\t\xFEW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\n\x14W__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\n%W__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\nWW__\xFD[PV[____`\x80\x85\x87\x03\x12\x15a\nmW__\xFD[\x845a\nx\x81a\n6V[\x93P` \x85\x015a\n\x88\x81a\n6V[\x92P`@\x85\x015\x91P``\x85\x015a\n\x9F\x81a\n6V[\x93\x96\x92\x95P\x90\x93PPV[___``\x84\x86\x03\x12\x15a\n\xBCW__\xFD[\x835a\n\xC7\x81a\n6V[\x92P` \x84\x015a\n\xD7\x81a\n6V[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0B\x07W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\n\xEDV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0B$W__\xFD[\x81Qa\t\x11\x81a\n6V[_` \x82\x84\x03\x12\x15a\x0B?W__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\t7W\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0B\x8EW__\xFD[\x81Q\x80\x15\x15\x81\x14a\t\x11W__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R4\x80\x15`\x0EW__\xFD[Pa\r\0\x80a\0\x1C_9_\xF3\xFE`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80cT.\xB7}\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\x0B\x01V[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\x0B\x9CV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\x0C\0V[a\t\x16V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\x0C>V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[\x81s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x03a\x03\xE4W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03@W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03d\x91\x90a\x0CjV[\x90P\x83\x81\x10\x15a\x03\xE2W_a\x03y\x82\x86a\x0C\x81V[\x90P\x80G\x10a\x03\xE0W\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\xD0\xE3\r\xB0\x82`@Q\x82c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01_`@Q\x80\x83\x03\x81\x85\x88\x80;\x15\x80\x15a\x03\xC8W__\xFD[PZ\xF1\x15\x80\x15a\x03\xDAW=__>=_\xFD[PPPPP[P[P[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04.W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04R\x91\x90a\x0C\xB9V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04\xC7W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\xEB\x91\x90a\x0CjV[\x90P\x84\x81\x10\x15a\x07HW`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05gW__\xFD[PZ\xF1\x92PPP\x80\x15a\x05xWP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x06\x0BW__\xFD[PZ\xF1\x92PPP\x80\x15a\x06\x1CWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x88\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06\x90W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\xB4\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\x07FW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x07\xB2W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x07\xD6\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\t\x0CWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x88a\x08\x07\x84\x8Aa\x0C\x81V[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x08oW__\xFD[PZ\xF1\x92PPP\x80\x15a\x08\x80WP`\x01[a\t\x0CW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPPV[a\t7s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\tV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\n\xE2W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\n\xE7V[``\x91P[P\x92P\x90P\x80a\n\xF9W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\x0B\x13W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0B0W__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\x0B@W__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BVW__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\x0BgW__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x0B\x99W__\xFD[PV[_____`\xA0\x86\x88\x03\x12\x15a\x0B\xB0W__\xFD[\x855a\x0B\xBB\x81a\x0BxV[\x94P` \x86\x015a\x0B\xCB\x81a\x0BxV[\x93P`@\x86\x015\x92P``\x86\x015a\x0B\xE2\x81a\x0BxV[\x91P`\x80\x86\x015a\x0B\xF2\x81a\x0BxV[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x0C\x12W__\xFD[\x835a\x0C\x1D\x81a\x0BxV[\x92P` \x84\x015a\x0C-\x81a\x0BxV[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0C]W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\x0CCV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0CzW__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\nyW\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0C\xC9W__\xFD[\x81Qa\nS\x81a\x0BxV[_` \x82\x84\x03\x12\x15a\x0C\xE4W__\xFD[\x81Q\x80\x15\x15\x81\x14a\nSW__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", ); /// The runtime bytecode of the contract, as deployed on the network. /// /// ```text - ///0x608060405260043610610037575f3560e01c80631626ba7e1461008d5780635bbe8b3f14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a73660046109bf565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610a5a565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610aaa565b6107d4565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610ae8565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5f8473ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ed573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103119190610b14565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015610386573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103aa9190610b2f565b905083811015610607576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610426575f5ffd5b505af1925050508015610437575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8087166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b1580156104ca575f5ffd5b505af19250505080156104db575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919087169063dd62ed3e90604401602060405180830381865afa15801561054f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105739190610b2f565b905084811015610605576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015610671573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106959190610b2f565b9050848110156107cb5773ffffffffffffffffffffffffffffffffffffffff841663494666b6876106c68489610b46565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561072e575f5ffd5b505af192505050801561073f575060015b6107cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b50505050505050565b6107f573ffffffffffffffffffffffffffffffffffffffff841683836107fa565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f9061088c90861683610904565b905061089781610918565b6108fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610911835f8461093d565b9392505050565b5f81515f14806109375750818060200190518101906109379190610b7e565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff1684846040516109669190610ae8565b5f6040518083038185875af1925050503d805f81146109a0576040519150601f19603f3d011682016040523d82523d5f602084013e6109a5565b606091505b5092509050806109b757815160208301fd5b509392505050565b5f5f5f604084860312156109d1575f5ffd5b83359250602084013567ffffffffffffffff8111156109ee575f5ffd5b8401601f810186136109fe575f5ffd5b803567ffffffffffffffff811115610a14575f5ffd5b866020828401011115610a25575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610a57575f5ffd5b50565b5f5f5f5f60808587031215610a6d575f5ffd5b8435610a7881610a36565b93506020850135610a8881610a36565b9250604085013591506060850135610a9f81610a36565b939692955090935050565b5f5f5f60608486031215610abc575f5ffd5b8335610ac781610a36565b92506020840135610ad781610a36565b929592945050506040919091013590565b5f82515f5b81811015610b075760208186018101518583015201610aed565b505f920191825250919050565b5f60208284031215610b24575f5ffd5b815161091181610a36565b5f60208284031215610b3f575f5ffd5b5051919050565b81810381811115610937577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610b8e575f5ffd5b81518015158114610911575f5ffdfea164736f6c634300081e000a + ///0x608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a /// ``` #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80c[\xBE\x8B?\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\t\xBFV[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\nZV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\n\xAAV[a\x07\xD4V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x02\xEDW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\x11\x91\x90a\x0B\x14V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x86\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03\x86W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03\xAA\x91\x90a\x0B/V[\x90P\x83\x81\x10\x15a\x06\x07W`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04&W__\xFD[PZ\xF1\x92PPP\x80\x15a\x047WP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x87\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x04\xCAW__\xFD[PZ\xF1\x92PPP\x80\x15a\x04\xDBWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x05OW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x05s\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x06\x05W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x87\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06qW=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\x95\x91\x90a\x0B/V[\x90P\x84\x81\x10\x15a\x07\xCBWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x87a\x06\xC6\x84\x89a\x0BFV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x07.W__\xFD[PZ\xF1\x92PPP\x80\x15a\x07?WP`\x01[a\x07\xCBW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPV[a\x07\xF5s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\x07\xFAV[PPPV[`@\x80Qs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x81\x16`$\x83\x01R`D\x80\x83\x01\x85\x90R\x83Q\x80\x84\x03\x90\x91\x01\x81R`d\x90\x92\x01\x90\x92R` \x81\x01\x80Q{\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x7F\t^\xA7\xB3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x17\x90R\x90_\x90a\x08\x8C\x90\x86\x16\x83a\t\x04V[\x90Pa\x08\x97\x81a\t\x18V[a\x08\xFDW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`\x1A`$\x82\x01R\x7FSafeERC20: approval failed\0\0\0\0\0\0`D\x82\x01R`d\x01a\x02\x9AV[PPPPPV[``a\t\x11\x83_\x84a\t=V[\x93\x92PPPV[_\x81Q_\x14\x80a\t7WP\x81\x80` \x01\x90Q\x81\x01\x90a\t7\x91\x90a\x0B~V[\x92\x91PPV[``_\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84\x84`@Qa\tf\x91\x90a\n\xE8V[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\t\xA0W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\t\xA5V[``\x91P[P\x92P\x90P\x80a\t\xB7W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\t\xD1W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\t\xEEW__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\t\xFEW__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\n\x14W__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\n%W__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\nWW__\xFD[PV[____`\x80\x85\x87\x03\x12\x15a\nmW__\xFD[\x845a\nx\x81a\n6V[\x93P` \x85\x015a\n\x88\x81a\n6V[\x92P`@\x85\x015\x91P``\x85\x015a\n\x9F\x81a\n6V[\x93\x96\x92\x95P\x90\x93PPV[___``\x84\x86\x03\x12\x15a\n\xBCW__\xFD[\x835a\n\xC7\x81a\n6V[\x92P` \x84\x015a\n\xD7\x81a\n6V[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0B\x07W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\n\xEDV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0B$W__\xFD[\x81Qa\t\x11\x81a\n6V[_` \x82\x84\x03\x12\x15a\x0B?W__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\t7W\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0B\x8EW__\xFD[\x81Q\x80\x15\x15\x81\x14a\t\x11W__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", + b"`\x80`@R`\x046\x10a\x007W_5`\xE0\x1C\x80c\x16&\xBA~\x14a\0\x8DW\x80cT.\xB7}\x14a\x01\x04W\x80c\xEBV%\xD9\x14a\x01%Wa\0>V[6a\0>W\0[_a\0\x83_6\x80\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847_\x92\x01\x91\x90\x91RPb\x01\0\0\x93\x92PPa\x01D\x90PV[\x90P\x80Q` \x82\x01\xF3[4\x80\x15a\0\x98W__\xFD[Pa\0\xCFa\0\xA76`\x04a\x0B\x01V[\x7F\x16&\xBA~\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x93\x92PPPV[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xF3[4\x80\x15a\x01\x0FW__\xFD[Pa\x01#a\x01\x1E6`\x04a\x0B\x9CV[a\x01\xC2V[\0[4\x80\x15a\x010W__\xFD[Pa\x01#a\x01?6`\x04a\x0C\0V[a\t\x16V[``_\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x83`@Qa\x01l\x91\x90a\x0C>V[_`@Q\x80\x83\x03\x81\x85Z\xF4\x91PP=\x80_\x81\x14a\x01\xA4W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\x01\xA9V[``\x91P[P\x92P\x90P\x80a\x01\xBBW\x81Q` \x83\x01\xFD[P\x92\x91PPV[\x7F\x02V]\xBA}h\xDC\xBE\xD6)\x11\0$\xB7\xB5\xE7\x85\xBF\xC1\xA4\x84` E\xEE\xA5\x13\xDE\x8A-\xCF\x99\x80T`\x01\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\0\x82\x16\x17\x90\x91U`\xFF\x16\x15a\x02\xA3W`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`#`$\x82\x01R\x7FprepareSwap can only be called o`D\x82\x01R\x7Fnce\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xFD[\x81s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x84s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16\x03a\x03\xE4W`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x86\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x03@W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x03d\x91\x90a\x0CjV[\x90P\x83\x81\x10\x15a\x03\xE2W_a\x03y\x82\x86a\x0C\x81V[\x90P\x80G\x10a\x03\xE0W\x83s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\xD0\xE3\r\xB0\x82`@Q\x82c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01_`@Q\x80\x83\x03\x81\x85\x88\x80;\x15\x80\x15a\x03\xC8W__\xFD[PZ\xF1\x15\x80\x15a\x03\xDAW=__>=_\xFD[PPPPP[P[P[_\x85s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x16c\x9BU,\xC2`@Q\x81c\xFF\xFF\xFF\xFF\x16`\xE0\x1B\x81R`\x04\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04.W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04R\x91\x90a\x0C\xB9V[`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x83\x16`$\x83\x01R\x91\x92P_\x91\x87\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x04\xC7W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x04\xEB\x91\x90a\x0CjV[\x90P\x84\x81\x10\x15a\x07HW`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R_`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x05gW__\xFD[PZ\xF1\x92PPP\x80\x15a\x05xWP`\x01[P`@Q\x7F\xEBV%\xD9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x88\x16`\x04\x83\x01R\x83\x16`$\x82\x01R\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF`D\x82\x01R0\x90c\xEBV%\xD9\x90`d\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x06\x0BW__\xFD[PZ\xF1\x92PPP\x80\x15a\x06\x1CWP`\x01[P`@Q\x7F\xDDb\xED>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x83\x81\x16`$\x83\x01R_\x91\x90\x88\x16\x90c\xDDb\xED>\x90`D\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x06\x90W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x06\xB4\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\x07FW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`*`$\x82\x01R\x7Ftrader did not give the required`D\x82\x01R\x7F approvals\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[P[`@Q\x7Fp\xA0\x821\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R0`\x04\x82\x01R_\x90s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x16\x90cp\xA0\x821\x90`$\x01` `@Q\x80\x83\x03\x81\x86Z\xFA\x15\x80\x15a\x07\xB2W=__>=_\xFD[PPPP`@Q=`\x1F\x19`\x1F\x82\x01\x16\x82\x01\x80`@RP\x81\x01\x90a\x07\xD6\x91\x90a\x0CjV[\x90P\x85\x81\x10\x15a\t\x0CWs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16cIFf\xB6\x88a\x08\x07\x84\x8Aa\x0C\x81V[`@Q\x7F\xFF\xFF\xFF\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\xE0\x85\x90\x1B\x16\x81Rs\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x90\x92\x16`\x04\x83\x01R`$\x82\x01R`D\x01_`@Q\x80\x83\x03\x81_\x87\x80;\x15\x80\x15a\x08oW__\xFD[PZ\xF1\x92PPP\x80\x15a\x08\x80WP`\x01[a\t\x0CW`@Q\x7F\x08\xC3y\xA0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x81R` `\x04\x82\x01R`&`$\x82\x01R\x7Ftrader does not have enough sell`D\x82\x01R\x7F token\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`d\x82\x01R`\x84\x01a\x02\x9AV[PPPPPPPPV[a\t7s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x84\x16\x83\x83a\tV[_`@Q\x80\x83\x03\x81\x85\x87Z\xF1\x92PPP=\x80_\x81\x14a\n\xE2W`@Q\x91P`\x1F\x19`?=\x01\x16\x82\x01`@R=\x82R=_` \x84\x01>a\n\xE7V[``\x91P[P\x92P\x90P\x80a\n\xF9W\x81Q` \x83\x01\xFD[P\x93\x92PPPV[___`@\x84\x86\x03\x12\x15a\x0B\x13W__\xFD[\x835\x92P` \x84\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0B0W__\xFD[\x84\x01`\x1F\x81\x01\x86\x13a\x0B@W__\xFD[\x805g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0BVW__\xFD[\x86` \x82\x84\x01\x01\x11\x15a\x0BgW__\xFD[\x93\x96` \x91\x90\x91\x01\x95P\x92\x93PPPV[s\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x16\x81\x14a\x0B\x99W__\xFD[PV[_____`\xA0\x86\x88\x03\x12\x15a\x0B\xB0W__\xFD[\x855a\x0B\xBB\x81a\x0BxV[\x94P` \x86\x015a\x0B\xCB\x81a\x0BxV[\x93P`@\x86\x015\x92P``\x86\x015a\x0B\xE2\x81a\x0BxV[\x91P`\x80\x86\x015a\x0B\xF2\x81a\x0BxV[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[___``\x84\x86\x03\x12\x15a\x0C\x12W__\xFD[\x835a\x0C\x1D\x81a\x0BxV[\x92P` \x84\x015a\x0C-\x81a\x0BxV[\x92\x95\x92\x94PPP`@\x91\x90\x91\x015\x90V[_\x82Q_[\x81\x81\x10\x15a\x0C]W` \x81\x86\x01\x81\x01Q\x85\x83\x01R\x01a\x0CCV[P_\x92\x01\x91\x82RP\x91\x90PV[_` \x82\x84\x03\x12\x15a\x0CzW__\xFD[PQ\x91\x90PV[\x81\x81\x03\x81\x81\x11\x15a\nyW\x7FNH{q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0_R`\x11`\x04R`$_\xFD[_` \x82\x84\x03\x12\x15a\x0C\xC9W__\xFD[\x81Qa\nS\x81a\x0BxV[_` \x82\x84\x03\x12\x15a\x0C\xE4W__\xFD[\x81Q\x80\x15\x15\x81\x14a\nSW__\xFD\xFE\xA1dsolcC\0\x08\x1E\0\n", ); #[derive(Default, Debug, PartialEq, Eq, Hash)] - /**Function with signature `ensureTradePreconditions(address,address,uint256,address)` and selector `0x5bbe8b3f`. + /**Function with signature `ensureTradePreconditions(address,address,uint256,address,address)` and selector `0x542eb77d`. ```solidity - function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address spardose) external; + function ensureTradePreconditions(address settlementContract, address sellToken, uint256 sellAmount, address nativeToken, address spardose) external; ```*/ #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -153,10 +158,12 @@ pub mod Trader { #[allow(missing_docs)] pub sellAmount: alloy_sol_types::private::primitives::aliases::U256, #[allow(missing_docs)] + pub nativeToken: alloy_sol_types::private::Address, + #[allow(missing_docs)] pub spardose: alloy_sol_types::private::Address, } ///Container type for the return parameters of the - /// [`ensureTradePreconditions(address,address,uint256, + /// [`ensureTradePreconditions(address,address,uint256,address, /// address)`](ensureTradePreconditionsCall) function. #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] @@ -177,6 +184,7 @@ pub mod Trader { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, + alloy_sol_types::sol_data::Address, ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( @@ -184,6 +192,7 @@ pub mod Trader { alloy_sol_types::private::Address, alloy_sol_types::private::primitives::aliases::U256, alloy_sol_types::private::Address, + alloy_sol_types::private::Address, ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] @@ -202,6 +211,7 @@ pub mod Trader { value.settlementContract, value.sellToken, value.sellAmount, + value.nativeToken, value.spardose, ) } @@ -214,7 +224,8 @@ pub mod Trader { settlementContract: tuple.0, sellToken: tuple.1, sellAmount: tuple.2, - spardose: tuple.3, + nativeToken: tuple.3, + spardose: tuple.4, } } } @@ -264,15 +275,16 @@ pub mod Trader { alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Uint<256>, alloy_sol_types::sol_data::Address, + alloy_sol_types::sol_data::Address, ); type Return = ensureTradePreconditionsReturn; type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; type ReturnTuple<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SELECTOR: [u8; 4] = [91u8, 190u8, 139u8, 63u8]; + const SELECTOR: [u8; 4] = [84u8, 46u8, 183u8, 125u8]; const SIGNATURE: &'static str = - "ensureTradePreconditions(address,address,uint256,address)"; + "ensureTradePreconditions(address,address,uint256,address,address)"; #[inline] fn new<'a>( @@ -293,6 +305,9 @@ pub mod Trader { as alloy_sol_types::SolType>::tokenize( &self.sellAmount, ), + ::tokenize( + &self.nativeToken, + ), ::tokenize( &self.spardose, ), @@ -664,7 +679,7 @@ pub mod Trader { /// Prefer using `SolInterface` methods instead. pub const SELECTORS: &'static [[u8; 4usize]] = &[ [22u8, 38u8, 186u8, 126u8], - [91u8, 190u8, 139u8, 63u8], + [84u8, 46u8, 183u8, 125u8], [235u8, 86u8, 37u8, 217u8], ]; /// The signatures in the same order as `SELECTORS`. @@ -1012,12 +1027,14 @@ pub mod Trader { settlementContract: alloy_sol_types::private::Address, sellToken: alloy_sol_types::private::Address, sellAmount: alloy_sol_types::private::primitives::aliases::U256, + nativeToken: alloy_sol_types::private::Address, spardose: alloy_sol_types::private::Address, ) -> alloy_contract::SolCallBuilder<&P, ensureTradePreconditionsCall, N> { self.call_builder(&ensureTradePreconditionsCall { settlementContract, sellToken, sellAmount, + nativeToken, spardose, }) } diff --git a/contracts/solidity/Solver.sol b/contracts/solidity/Solver.sol index 0698e0a47d..b141f28653 100644 --- a/contracts/solidity/Solver.sol +++ b/contracts/solidity/Solver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; -import { IERC20 } from "./interfaces/IERC20.sol"; +import { IERC20, INativeERC20 } from "./interfaces/IERC20.sol"; import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol"; import { Caller } from "./libraries/Caller.sol"; import { Math } from "./libraries/Math.sol"; @@ -117,6 +117,7 @@ contract Solver layout at 0x14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff1 ISettlement settlementContract, address sellToken, uint256 sellAmount, + address nativeToken, address spardose ) external { uint256 gasStart = gasleft(); @@ -124,6 +125,7 @@ contract Solver layout at 0x14f5b2c185fc03c75c787d1f0e10ea137cc6d235a0047448eff1 settlementContract, sellToken, sellAmount, + nativeToken, spardose ); // Account for costs of gas used outside of metered section. diff --git a/contracts/solidity/Trader.sol b/contracts/solidity/Trader.sol index be5dcdc158..f73e805ec9 100644 --- a/contracts/solidity/Trader.sol +++ b/contracts/solidity/Trader.sol @@ -55,15 +55,34 @@ contract Trader layout at 0x02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea5 /// a stable address in tests. /// @param sellToken - token being sold by the trade /// @param sellAmount - expected amount to be sold according to the quote + /// @param nativeToken - ERC20 version of the chain's native token /// @param spardose - piggy bank for requesting additional funds function ensureTradePreconditions( ISettlement settlementContract, address sellToken, uint256 sellAmount, + address nativeToken, address spardose ) external { require(!triggerInitialization(), "prepareSwap can only be called once"); + if (sellToken == nativeToken) { + uint256 availableNativeToken = IERC20(sellToken).balanceOf(address(this)); + if (availableNativeToken < sellAmount) { + uint256 amountToWrap = sellAmount - availableNativeToken; + // If the user has sufficient balance, simulate the wrapping the missing + // `ETH` so the user doesn't have to spend gas on that just to get a quote. + // If they are happy with the quote and want to create an order they will + // actually have to do the wrapping, though. Note that we don't attempt to + // wrap if the user doesn't have sufficient `ETH` balance, since that would + // revert. Instead, we fall-through so that we handle insufficient sell + // token balances uniformly for all tokens. + if (address(this).balance >= amountToWrap) { + INativeERC20(nativeToken).deposit{value: amountToWrap}(); + } + } + } + address vaultRelayer = settlementContract.vaultRelayer(); uint256 currentAllowance = IERC20(sellToken).allowance(address(this), vaultRelayer); if (currentAllowance < sellAmount) { diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index eb12ee6466..9c3325991c 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -570,6 +570,7 @@ impl TradeVerifier { settlementContract: self.simulator.settlement_address(), sellToken: query.sell_token, sellAmount: sell_amount, + nativeToken: self.simulator.native_token(), spardose: Self::SPARDOSE, } .abi_encode(); From c6f42f88cfd30716e91fc9b52a9b20c7863be04d Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Tue, 5 May 2026 17:30:34 +0100 Subject: [PATCH 119/154] Fix merge resolution after base branch API rename - Restore the single shared SettlementSimulator that 933874300 introduced. The merge brought back the old two-block layout where the eip1271 path used unwrap_or_default() for the flashloan router, silently falling back to the zero address on chains without a deployment. - Update simulator call sites to the new API in the base branch: add_orders -> with_orders, with_overrides([BuyTokensForBuffers]) -> provide_sufficient_buy_tokens(). Affects orderbook eip1271_simulation and the aave_replay test. - Restore the "Invalid kind enum value" comment on the http_validation test, which the merge replaced with text that did not match the body. --- crates/e2e/tests/e2e/malformed_requests.rs | 2 +- crates/orderbook/src/eip1271_simulation.rs | 5 ++-- crates/orderbook/src/run.rs | 31 +--------------------- crates/simulator/tests/aave_replay.rs | 5 ++-- 4 files changed, 6 insertions(+), 37 deletions(-) diff --git a/crates/e2e/tests/e2e/malformed_requests.rs b/crates/e2e/tests/e2e/malformed_requests.rs index 5e699c5f9d..5a8e5baf44 100644 --- a/crates/e2e/tests/e2e/malformed_requests.rs +++ b/crates/e2e/tests/e2e/malformed_requests.rs @@ -460,7 +460,7 @@ async fn http_validation(web3: Web3) { "zero sellAmount should return 422" ); - // some fields missing → 422 + // Invalid kind enum value → 422 let response = client .post(format!("{API_HOST}/restricted/api/v1/debug/simulation")) .json(&json!({ diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs index c98eda3b87..1f18af9346 100644 --- a/crates/orderbook/src/eip1271_simulation.rs +++ b/crates/orderbook/src/eip1271_simulation.rs @@ -5,7 +5,6 @@ use { shared::order_validation::{Eip1271Simulating, Eip1271SimulationError}, simulator::simulation_builder::{ self, - AccountOverrideRequest, Block, ExecutionAmount, PriceEncoding, @@ -37,13 +36,13 @@ impl Eip1271Simulating for OrderSimulatorAdapter { let inputs = self .inner .new_simulation_builder() - .add_orders([simulation_builder::Order::new(order.data) + .with_orders([simulation_builder::Order::new(order.data) .with_signature(order.metadata.owner, order.signature.clone()) .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) .parameters_from_app_data(&full_app_data) .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("parse app data")))? .from_solver(Solver::Fake(None)) - .with_overrides([AccountOverrideRequest::BuyTokensForBuffers]) + .provide_sufficient_buy_tokens() .at_block(Block::Latest) .build() .await diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 81ebaceeec..1d61148293 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -401,12 +401,10 @@ pub async fn run(config: Configuration) { chain.id().to_string(), )) as _ }); - let flash_loan_router_address = - contracts::FlashLoanRouter::deployment_address(&chain.id()).unwrap_or_default(); Some( simulator::simulation_builder::SettlementSimulator::new( settlement_contract.clone(), - flash_loan_router_address, + flashloan_router_address, hooks_trampoline_address, *native_token.address(), sim_config.gas_limit.saturating_to(), @@ -480,33 +478,6 @@ pub async fn run(config: Configuration) { ipfs, )); - let order_simulator = if let Some(config) = config.order_simulation { - let tenderly: Option> = - config.tenderly.as_ref().map(|tenderly_config| { - Arc::new(simulator::tenderly::TenderlyApi::new( - tenderly_config, - &http_factory, - chain.id().to_string(), - )) as _ - }); - Some( - simulator::simulation_builder::SettlementSimulator::new( - settlement_contract.clone(), - flashloan_router_address, - hooks_trampoline_address, - *native_token.address(), - config.gas_limit.saturating_to(), - balance_overrider.clone(), - current_block_stream.clone(), - tenderly, - ) - .await - .expect("failed to initialize SettlementSimulator"), - ) - } else { - None - }; - let orderbook = Arc::new(Orderbook::new( domain_separator, *settlement_contract.address(), diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 6be360f2b1..f4b4f023d0 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -7,7 +7,6 @@ use { }, simulator::simulation_builder::{ self, - AccountOverrideRequest, Block, EthCallInputs, ExecutionAmount, @@ -210,13 +209,13 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI simulator .new_simulation_builder() - .add_orders([simulation_builder::Order::new(order_data) + .with_orders([simulation_builder::Order::new(order_data) .with_signature(order_owner, Signature::Eip1271(signature_bytes)) .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) .parameters_from_app_data(full_app_data) .expect("parameters_from_app_data should parse the app data") .from_solver(Solver::Fake(None)) - .with_overrides([AccountOverrideRequest::BuyTokensForBuffers]) + .provide_sufficient_buy_tokens() .at_block(Block::Number(fork_block_mainnet)) .build() .await From 70885e8ee1f658d9b5e9e5c445d81805218a2f43 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 11:54:56 +0000 Subject: [PATCH 120/154] group errors together --- crates/simulator/src/simulation_builder.rs | 44 +++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 9941b5f9b1..b4dcf95528 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -495,28 +495,6 @@ impl EthCallInputs { } } -#[derive(Debug, thiserror::Error)] -pub enum ConversionError { - #[error("simulation does not have a target")] - MissingTo, - #[error("could not convert state overrides")] - StateOverrides, -} - -#[derive(Debug, thiserror::Error)] -pub enum BuildError { - #[error("no order was added")] - NoOrder, - #[error("no solver was set")] - NoSolver, - #[error("failed to query filled amount from settlement contract: {0}")] - FilledAmountQuery(#[source] anyhow::Error), - #[error("failed to parse app data: {0}")] - AppDataParse(#[source] serde_json::Error), - #[error("both wrappers and flashloans cannot be encoded in the same settlement")] - FlashloanWrappersIncompatible, -} - #[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. @@ -539,6 +517,28 @@ pub enum AccountOverrideRequest { // TODO: add Allowance } +#[derive(Debug, thiserror::Error)] +pub enum ConversionError { + #[error("simulation does not have a target")] + MissingTo, + #[error("could not convert state overrides")] + StateOverrides, +} + +#[derive(Debug, thiserror::Error)] +pub enum BuildError { + #[error("no order was added")] + NoOrder, + #[error("no solver was set")] + NoSolver, + #[error("failed to query filled amount from settlement contract: {0}")] + FilledAmountQuery(#[source] anyhow::Error), + #[error("failed to parse app data: {0}")] + AppDataParse(#[source] serde_json::Error), + #[error("both wrappers and flashloans cannot be encoded in the same settlement")] + FlashloanWrappersIncompatible, +} + /// Error returned when two [`AccountOverride`]s set the same field for the same /// address and cannot be merged. #[derive(Debug, thiserror::Error)] From 7192c76ebac3b737979c7bc8856ab01c917f6fe3 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 11:58:02 +0000 Subject: [PATCH 121/154] doc comments --- crates/simulator/src/simulation_builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b4dcf95528..e7a586228a 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -517,6 +517,8 @@ pub enum AccountOverrideRequest { // TODO: add Allowance } +/// Error returned when a built eth_call simulation could not be converted +/// into a tenderly simulation request. #[derive(Debug, thiserror::Error)] pub enum ConversionError { #[error("simulation does not have a target")] @@ -525,6 +527,8 @@ pub enum ConversionError { StateOverrides, } +/// Error returned when data needed to build the final simulation was missing, +/// incompatible, or could not be computed ad-hoc. #[derive(Debug, thiserror::Error)] pub enum BuildError { #[error("no order was added")] From 8ccf915b05c93beca81b6bc9daf9d8f205db5ed8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:02:00 +0000 Subject: [PATCH 122/154] drop comment on tenderly gas price - should not be needed --- crates/simulator/src/simulation_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index e7a586228a..8d2cac9fb4 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -487,7 +487,7 @@ impl EthCallInputs { ), access_list: self.request.access_list.as_ref().map(Into::into), save: Some(true), - gas_price: None, // use tenderly default for now + gas_price: None, save_if_fails: Some(true), transaction_index: None, generate_access_list: None, From f7be2aac0ad3fb03be01b5d3b3d6aa18cb268aac Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:03:14 +0000 Subject: [PATCH 123/154] import `tenderly` instead of using fully qualified paths --- crates/simulator/src/simulation_builder.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 8d2cac9fb4..f5111d26ae 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -1,5 +1,5 @@ use { - crate::{encoding::WrapperCall, tenderly::dto::StateObject}, + crate::{encoding::WrapperCall, tenderly}, alloy_primitives::{Address, B256, Bytes, TxKind, U256}, alloy_provider::{DynProvider, Provider}, alloy_rpc_types::{ @@ -38,7 +38,7 @@ pub(crate) struct Inner { pub(crate) domain_separator: DomainSeparator, pub(crate) chain_id: u64, pub(crate) current_block: CurrentBlockWatcher, - pub(crate) tenderly: Option>, + pub(crate) tenderly: Option>, } impl SettlementSimulator { @@ -51,7 +51,7 @@ impl SettlementSimulator { max_gas_limit: u64, balance_overrides: Arc, current_block: CurrentBlockWatcher, - tenderly: Option>, + tenderly: Option>, ) -> Result { let authenticator = Address(settlement.authenticator().call().await?.0); let domain_separator = DomainSeparator(settlement.domainSeparator().call().await?.0); @@ -404,7 +404,7 @@ pub struct EthCallInputs { #[derive(Clone, Debug)] pub struct TenderlyReport { /// Full request object that can be used directly with the Tenderly API - pub tenderly_request: crate::tenderly::dto::Request, + pub tenderly_request: tenderly::dto::Request, /// Shared Tenderly simulation URL for debugging in the dashboard pub tenderly_url: Option, /// Any error that might have been reported during order simulation @@ -451,8 +451,8 @@ impl EthCallInputs { /// Converts the simulation into a request that can be simulated with /// tenderly. - pub fn to_tenderly_request(&self) -> Result { - Ok(crate::tenderly::dto::Request { + pub fn to_tenderly_request(&self) -> Result { + Ok(tenderly::dto::Request { // By default tenderly simulates calls at the start of the block. So if we simulate // something when the latest block is `n` we need to tell tenderly to simulate at // block `n+1` to still have all of block n's txs happen before our simulation runs. @@ -472,14 +472,14 @@ impl EthCallInputs { .unwrap_or_default(), gas: self.request.gas, value: self.request.value, - simulation_type: Some(crate::tenderly::dto::SimulationType::Full), + simulation_type: Some(tenderly::dto::SimulationType::Full), state_objects: Some( self.state_overrides .iter() .map(|(key, value)| { Ok(( *key, - StateObject::try_from(value.clone()) + tenderly::dto::StateObject::try_from(value.clone()) .map_err(|_| ConversionError::StateOverrides)?, )) }) From ed7ef9a151d51119ddc525c38318a9e0b3cad94c Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:04:47 +0000 Subject: [PATCH 124/154] cleaner error conversion --- crates/simulator/src/simulation_builder.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f5111d26ae..f76aad4243 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -437,15 +437,10 @@ impl EthCallInputs { None }; - let simulation_result = self.simulate().await; - Ok(TenderlyReport { tenderly_request, tenderly_url, - error: match simulation_result { - Ok(_) => None, - Err(err) => Some(err.to_string()), - }, + error: self.simulate().await.err().map(|err| err.to_string()), }) } From 7c0521f2ac631a0726bd0a8ee130f46052562722 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:08:56 +0000 Subject: [PATCH 125/154] doc comments --- crates/simulator/src/simulation_builder.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f76aad4243..05fdedbaf0 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -398,20 +398,9 @@ pub struct EthCallInputs { pub block: u64, } -/// The result of Order simulation, contains the error (if any) -/// and full Tenderly API request that can be used to resimulate -/// and debug using Tenderly -#[derive(Clone, Debug)] -pub struct TenderlyReport { - /// Full request object that can be used directly with the Tenderly API - pub tenderly_request: tenderly::dto::Request, - /// Shared Tenderly simulation URL for debugging in the dashboard - pub tenderly_url: Option, - /// Any error that might have been reported during order simulation - pub error: Option, -} - impl EthCallInputs { + /// Runs the generated simulation using an `eth_call` and returns the response bytes if + /// there are any. pub async fn simulate(self) -> Result> { self.simulator .0 @@ -423,6 +412,9 @@ impl EthCallInputs { .await } + /// Same as [`EthCallInputs::simulate`] but also generates a tenderly request in case + /// one wants to re-simulate with tenderly. If tenderly credentials are configured this + /// even generates a shareable link for the simulation. pub async fn simulate_with_tenderly_report(self) -> Result { let tenderly_request = self .to_tenderly_request() From 3098ef297968a176ddd5bb2442bcbc46b6e95968 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:09:02 +0000 Subject: [PATCH 126/154] move struct + impl together --- crates/simulator/src/simulation_builder.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 05fdedbaf0..078647f798 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -482,6 +482,19 @@ impl EthCallInputs { } } +/// The result of Order simulation, contains the error (if any) +/// and full Tenderly API request that can be used to resimulate +/// and debug using Tenderly +#[derive(Clone, Debug)] +pub struct TenderlyReport { + /// Full request object that can be used directly with the Tenderly API + pub tenderly_request: tenderly::dto::Request, + /// Shared Tenderly simulation URL for debugging in the dashboard + pub tenderly_url: Option, + /// Any error that might have been reported during order simulation + pub error: Option, +} + #[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. From c74823f727a0f098560f8b827c7fa2d34e03faab Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:10:09 +0000 Subject: [PATCH 127/154] move ethcallinputs to simulationbuilder --- crates/simulator/src/simulation_builder.rs | 213 +++++++++++---------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 078647f798..de0a5e09fe 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -276,6 +276,113 @@ impl SimulationBuilder { } } +/// The output of [`SimulationBuilder::build`]: a transaction request and state +/// overrides ready to be passed to an alloy provider for simulation. +pub struct EthCallInputs { + pub request: TransactionRequest, + pub state_overrides: StateOverride, + pub simulator: SettlementSimulator, + pub block: u64, +} + +impl EthCallInputs { + /// Runs the generated simulation using an `eth_call` and returns the response bytes if + /// there are any. + pub async fn simulate(self) -> Result> { + self.simulator + .0 + .provider + .clone() + .call(self.request) + .overrides(self.state_overrides) + .block(self.block.into()) + .await + } + + /// Same as [`EthCallInputs::simulate`] but also generates a tenderly request in case + /// one wants to re-simulate with tenderly. If tenderly credentials are configured this + /// even generates a shareable link for the simulation. + pub async fn simulate_with_tenderly_report(self) -> Result { + let tenderly_request = self + .to_tenderly_request() + .context("failed to convert to tenderly request")?; + + let tenderly_url = if let Some(api) = &self.simulator.0.tenderly { + api.simulate_and_share(tenderly_request.clone()) + .await + .inspect_err(|err| tracing::warn!(?err, "failed to simulate via tenderly")) + .ok() + } else { + None + }; + + Ok(TenderlyReport { + tenderly_request, + tenderly_url, + error: self.simulate().await.err().map(|err| err.to_string()), + }) + } + + /// Converts the simulation into a request that can be simulated with + /// tenderly. + pub fn to_tenderly_request(&self) -> Result { + Ok(tenderly::dto::Request { + // By default tenderly simulates calls at the start of the block. So if we simulate + // something when the latest block is `n` we need to tell tenderly to simulate at + // block `n+1` to still have all of block n's txs happen before our simulation runs. + block_number: Some(self.block + 1), + network_id: self.simulator.0.chain_id.to_string(), + from: self.request.from.unwrap_or_default(), + to: match &self.request.to.ok_or(ConversionError::MissingTo)? { + TxKind::Create => Default::default(), + TxKind::Call(to) => *to, + }, + input: self + .request + .input + .input + .as_ref() + .map(|bytes| bytes.to_vec()) + .unwrap_or_default(), + gas: self.request.gas, + value: self.request.value, + simulation_type: Some(tenderly::dto::SimulationType::Full), + state_objects: Some( + self.state_overrides + .iter() + .map(|(key, value)| { + Ok(( + *key, + tenderly::dto::StateObject::try_from(value.clone()) + .map_err(|_| ConversionError::StateOverrides)?, + )) + }) + .collect::>()?, + ), + access_list: self.request.access_list.as_ref().map(Into::into), + save: Some(true), + gas_price: None, + save_if_fails: Some(true), + transaction_index: None, + generate_access_list: None, + }) + } +} + +/// The result of Order simulation, contains the error (if any) +/// and full Tenderly API request that can be used to resimulate +/// and debug using Tenderly +#[derive(Clone, Debug)] +pub struct TenderlyReport { + /// Full request object that can be used directly with the Tenderly API + pub tenderly_request: tenderly::dto::Request, + /// Shared Tenderly simulation URL for debugging in the dashboard + pub tenderly_url: Option, + /// Any error that might have been reported during order simulation + pub error: Option, +} + + pub enum Solver { /// Simulation assumes this is an actual solver so no state overrides will /// be applied to allow list it explicitly. @@ -389,112 +496,6 @@ impl Order { } } -/// The output of [`SimulationBuilder::build`]: a transaction request and state -/// overrides ready to be passed to an alloy provider for simulation. -pub struct EthCallInputs { - pub request: TransactionRequest, - pub state_overrides: StateOverride, - pub simulator: SettlementSimulator, - pub block: u64, -} - -impl EthCallInputs { - /// Runs the generated simulation using an `eth_call` and returns the response bytes if - /// there are any. - pub async fn simulate(self) -> Result> { - self.simulator - .0 - .provider - .clone() - .call(self.request) - .overrides(self.state_overrides) - .block(self.block.into()) - .await - } - - /// Same as [`EthCallInputs::simulate`] but also generates a tenderly request in case - /// one wants to re-simulate with tenderly. If tenderly credentials are configured this - /// even generates a shareable link for the simulation. - pub async fn simulate_with_tenderly_report(self) -> Result { - let tenderly_request = self - .to_tenderly_request() - .context("failed to convert to tenderly request")?; - - let tenderly_url = if let Some(api) = &self.simulator.0.tenderly { - api.simulate_and_share(tenderly_request.clone()) - .await - .inspect_err(|err| tracing::warn!(?err, "failed to simulate via tenderly")) - .ok() - } else { - None - }; - - Ok(TenderlyReport { - tenderly_request, - tenderly_url, - error: self.simulate().await.err().map(|err| err.to_string()), - }) - } - - /// Converts the simulation into a request that can be simulated with - /// tenderly. - pub fn to_tenderly_request(&self) -> Result { - Ok(tenderly::dto::Request { - // By default tenderly simulates calls at the start of the block. So if we simulate - // something when the latest block is `n` we need to tell tenderly to simulate at - // block `n+1` to still have all of block n's txs happen before our simulation runs. - block_number: Some(self.block + 1), - network_id: self.simulator.0.chain_id.to_string(), - from: self.request.from.unwrap_or_default(), - to: match &self.request.to.ok_or(ConversionError::MissingTo)? { - TxKind::Create => Default::default(), - TxKind::Call(to) => *to, - }, - input: self - .request - .input - .input - .as_ref() - .map(|bytes| bytes.to_vec()) - .unwrap_or_default(), - gas: self.request.gas, - value: self.request.value, - simulation_type: Some(tenderly::dto::SimulationType::Full), - state_objects: Some( - self.state_overrides - .iter() - .map(|(key, value)| { - Ok(( - *key, - tenderly::dto::StateObject::try_from(value.clone()) - .map_err(|_| ConversionError::StateOverrides)?, - )) - }) - .collect::>()?, - ), - access_list: self.request.access_list.as_ref().map(Into::into), - save: Some(true), - gas_price: None, - save_if_fails: Some(true), - transaction_index: None, - generate_access_list: None, - }) - } -} - -/// The result of Order simulation, contains the error (if any) -/// and full Tenderly API request that can be used to resimulate -/// and debug using Tenderly -#[derive(Clone, Debug)] -pub struct TenderlyReport { - /// Full request object that can be used directly with the Tenderly API - pub tenderly_request: tenderly::dto::Request, - /// Shared Tenderly simulation URL for debugging in the dashboard - pub tenderly_url: Option, - /// Any error that might have been reported during order simulation - pub error: Option, -} - #[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. From 036ed921e17b6c03909f40480977a31633a1dfd8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:10:56 +0000 Subject: [PATCH 128/154] fixup --- crates/simulator/src/simulation_builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index de0a5e09fe..5336a531b2 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -382,7 +382,6 @@ pub struct TenderlyReport { pub error: Option, } - pub enum Solver { /// Simulation assumes this is an actual solver so no state overrides will /// be applied to allow list it explicitly. From a37050b97511ac7df10d6d5d100ceed274cd8c8b Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:11:11 +0000 Subject: [PATCH 129/154] move struct + impl together --- crates/simulator/src/simulation_builder.rs | 31 +++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 5336a531b2..b62ce4abe4 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -438,21 +438,6 @@ pub struct Order { pub(crate) price_encoding: PriceEncoding, } -/// Configuration for wrapping the settlement in a flashloan or custom wrapper -/// contract chain. -pub enum WrapperConfig { - Flashloan(Vec), - Custom(Vec), - NoWrapper, -} - -pub struct FlashloanRequest { - pub amount: U256, - pub borrower: Address, - pub lender: Address, - pub token: Address, -} - impl Order { pub fn new(data: OrderData) -> Self { Self { @@ -495,6 +480,22 @@ impl Order { } } +/// Configuration for wrapping the settlement in a flashloan or custom wrapper +/// contract chain. +pub enum WrapperConfig { + Flashloan(Vec), + Custom(Vec), + NoWrapper, +} + +pub struct FlashloanRequest { + pub amount: U256, + pub borrower: Address, + pub lender: Address, + pub token: Address, +} + + #[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. From f07d4d127091c30b7832974c40f1f1c4b6f92c95 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:15:19 +0000 Subject: [PATCH 130/154] Function for wrapping hooks in trampoline call --- crates/simulator/src/simulation_builder.rs | 60 ++++++++++++---------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index b62ce4abe4..f6d0ea2ce3 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -195,28 +195,8 @@ impl SimulationBuilder { pub fn parameters_from_app_data(mut self, app_data: &str) -> Result { let protocol = app_data::parse(app_data.as_bytes()).map_err(BuildError::AppDataParse)?; - let encode_hooks = |hooks: &[app_data::Hook]| -> Vec { - if hooks.is_empty() { - return vec![]; - } - vec![InteractionData { - target: self.simulator.0.hooks_trampoline, - value: U256::ZERO, - call_data: contracts::HooksTrampoline::HooksTrampoline::executeCall { - hooks: hooks - .iter() - .map(|h| contracts::HooksTrampoline::HooksTrampoline::Hook { - target: h.target, - callData: Bytes::copy_from_slice(&h.call_data), - gasLimit: U256::from(h.gas_limit), - }) - .collect(), - } - .abi_encode(), - }] - }; - self.pre_interactions = encode_hooks(&protocol.hooks.pre); - self.post_interactions = encode_hooks(&protocol.hooks.post); + self.pre_interactions = self.encode_hooks(&protocol.hooks.pre); + self.post_interactions = self.encode_hooks(&protocol.hooks.post); let has_wrappers = !protocol.wrappers.is_empty(); let has_flashloan = protocol.flashloan.is_some(); @@ -246,6 +226,30 @@ impl SimulationBuilder { Ok(self) } + /// Generates 1 interaction executing the given hooks via the trampoline + /// contract since executing hooks directly from the settlement contract + /// context would give them elevated priviliges that puts funds at risk. + fn encode_hooks(&self, hooks: &[app_data::Hook]) -> Vec { + if hooks.is_empty() { + return vec![]; + } + vec![InteractionData { + target: self.simulator.0.hooks_trampoline, + value: U256::ZERO, + call_data: contracts::HooksTrampoline::HooksTrampoline::executeCall { + hooks: hooks + .iter() + .map(|h| contracts::HooksTrampoline::HooksTrampoline::Hook { + target: h.target, + callData: Bytes::copy_from_slice(&h.call_data), + gasLimit: U256::from(h.gas_limit), + }) + .collect(), + } + .abi_encode(), + }] + } + /// Instructs the builder to override the settlement contract's buy-token /// balances so it can pay out every order. The exact amounts are derived /// from the clearing prices and executed amounts once @@ -286,8 +290,8 @@ pub struct EthCallInputs { } impl EthCallInputs { - /// Runs the generated simulation using an `eth_call` and returns the response bytes if - /// there are any. + /// Runs the generated simulation using an `eth_call` and returns the + /// response bytes if there are any. pub async fn simulate(self) -> Result> { self.simulator .0 @@ -299,9 +303,10 @@ impl EthCallInputs { .await } - /// Same as [`EthCallInputs::simulate`] but also generates a tenderly request in case - /// one wants to re-simulate with tenderly. If tenderly credentials are configured this - /// even generates a shareable link for the simulation. + /// Same as [`EthCallInputs::simulate`] but also generates a tenderly + /// request in case one wants to re-simulate with tenderly. If tenderly + /// credentials are configured this even generates a shareable link for + /// the simulation. pub async fn simulate_with_tenderly_report(self) -> Result { let tenderly_request = self .to_tenderly_request() @@ -495,7 +500,6 @@ pub struct FlashloanRequest { pub token: Address, } - #[derive(Debug)] pub enum AccountOverrideRequest { /// Gives the address a huge amount of ETH. From 2ef69f7fa2af4d293cbfb21b4047358bc9a08617 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:18:40 +0000 Subject: [PATCH 131/154] match instead of ifs --- crates/simulator/src/simulation_builder.rs | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index f6d0ea2ce3..a9ad5fe0e3 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -198,29 +198,29 @@ impl SimulationBuilder { self.pre_interactions = self.encode_hooks(&protocol.hooks.pre); self.post_interactions = self.encode_hooks(&protocol.hooks.post); - let has_wrappers = !protocol.wrappers.is_empty(); - let has_flashloan = protocol.flashloan.is_some(); - if has_wrappers && has_flashloan { - return Err(BuildError::FlashloanWrappersIncompatible); - } - if has_wrappers { - self.wrapper = WrapperConfig::Custom( - protocol - .wrappers - .into_iter() - .map(|w| WrapperCall { - address: w.address, - data: w.data.into(), - }) - .collect(), - ); - } else if let Some(flashloan) = protocol.flashloan { - self.wrapper = WrapperConfig::Flashloan(vec![FlashloanRequest { - amount: flashloan.amount, - borrower: flashloan.protocol_adapter, - lender: flashloan.liquidity_provider, - token: flashloan.token, - }]); + match (protocol.wrappers.is_empty(), protocol.flashloan) { + (false, Some(_)) => return Err(BuildError::FlashloanWrappersIncompatible), + (false, None) => { + self.wrapper = WrapperConfig::Custom( + protocol + .wrappers + .into_iter() + .map(|w| WrapperCall { + address: w.address, + data: w.data.into(), + }) + .collect(), + ); + } + (true, Some(flashloan)) => { + self.wrapper = WrapperConfig::Flashloan(vec![FlashloanRequest { + amount: flashloan.amount, + borrower: flashloan.protocol_adapter, + lender: flashloan.liquidity_provider, + token: flashloan.token, + }]); + } + (true, None) => {} } Ok(self) From 667a4c78c543d75ee90b3b5dc069e813c266e431 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:21:40 +0000 Subject: [PATCH 132/154] doc comment --- crates/simulator/src/encoding.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index 1d897f707c..122b7a68ae 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -507,6 +507,11 @@ pub(crate) async fn finish_simulation_builder( }) } +/// Computes the exact amount the order should be filled with +/// based on the configured [`ExecutionAmount`]. +/// If `Remaining` is configured we look up how much the order +/// was already filled in the settlement contract and deduct +/// that from the full amount. async fn executed_amount( builder: &SimulationBuilder, order: &Order, From 4a7f75e5c0e58d85323f869b2badc476d7f20e4f Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:23:17 +0000 Subject: [PATCH 133/154] use `vec![]` instead of pushing into empty vector --- .../src/trade_verifier/mod.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 9c3325991c..6d36807bf8 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -120,7 +120,7 @@ impl TradeVerifier { verification.from = Address::random(); tracing::debug!( trader = ?verification.from, - "use random trader address with fake balances" + "using random trader address with fake balances" ); } @@ -475,16 +475,17 @@ impl TradeVerifier { query: &PriceQuery, trade: &TradeKind, ) -> Result> { - let mut requests: Vec = Vec::new(); + let mut requests = vec![ + // Setup the funding contract override. Regardless of whether or not the + // contract has funds, it needs to exist in order to not revert + // simulations (Solidity reverts on attempts to call addresses without + // any code). + AccountOverrideRequest::Code { + account: Self::SPARDOSE, + code: Spardose::Spardose::DEPLOYED_BYTECODE.clone(), + }, + ]; - // Setup the funding contract override. Regardless of whether or not the - // contract has funds, it needs to exist in order to not revert - // simulations (Solidity reverts on attempts to call addresses without - // any code). - requests.push(AccountOverrideRequest::Code { - account: Self::SPARDOSE, - code: Spardose::Spardose::DEPLOYED_BYTECODE.clone(), - }); // Provide mocked balances if possible to the spardose to allow it to // give some balances to the trader in order to verify trades even for // owners without balances. Note that we use a separate account for From 16cd8f3a989dca1c8575aa4600c1b60c1449cc68 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:28:02 +0000 Subject: [PATCH 134/154] use iterators intead of eagerly collecting into vectors --- crates/price-estimation/src/trade_verifier/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 6d36807bf8..268f393e0a 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -189,7 +189,7 @@ impl TradeVerifier { }; // pre: [verification.pre, trade.pre, trade_setup, storeBalance_before] - let pre_interactions: Vec = map_interactions_data( + let pre_interactions = map_interactions_data( verification .pre_interactions .iter() @@ -197,8 +197,7 @@ impl TradeVerifier { ) .into_iter() .chain([self.trade_setup_interaction(out_amount, &verification, query, trade)]) - .chain([store_balance.clone()]) - .collect(); + .chain([store_balance.clone()]); // WETH unwrap so ETH buy orders can pay out native tokens let weth_unwrap = (query.buy_token == BUY_ETH_ADDRESS).then(|| InteractionData { @@ -207,15 +206,13 @@ impl TradeVerifier { call_data: WETH9::WETH9::withdrawCall { wad: buy_amount }.abi_encode(), }); // main: [trade.main, weth_unwrap] - let main_interactions: Vec = map_interactions_data(trade.interactions()) + let main_interactions = map_interactions_data(trade.interactions()) .into_iter() - .chain(weth_unwrap) - .collect(); + .chain(weth_unwrap); // post: [storeBalance_after, verification.post] - let post_interactions: Vec = std::iter::once(store_balance) - .chain(map_interactions_data(verification.post_interactions.iter())) - .collect(); + let post_interactions = std::iter::once(store_balance) + .chain(map_interactions_data(&verification.post_interactions)); // Set limit amounts to always pass the settlement check so the actual // out_amount can be measured via the storeBalance interactions. From e16f2da3f8432b095a8e4a27493ece75b860a553 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 12:59:24 +0000 Subject: [PATCH 135/154] Use `#[from]` over `#[source]` --- crates/simulator/src/simulation_builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index a9ad5fe0e3..e6c19012ed 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -541,9 +541,9 @@ pub enum BuildError { #[error("no solver was set")] NoSolver, #[error("failed to query filled amount from settlement contract: {0}")] - FilledAmountQuery(#[source] anyhow::Error), + FilledAmountQuery(#[from] anyhow::Error), #[error("failed to parse app data: {0}")] - AppDataParse(#[source] serde_json::Error), + AppDataParse(#[from] serde_json::Error), #[error("both wrappers and flashloans cannot be encoded in the same settlement")] FlashloanWrappersIncompatible, } From 88b8e73fc1d08503f596ff5dbaa874f2bbe850c8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 13:02:47 +0000 Subject: [PATCH 136/154] better errors --- crates/price-estimation/src/trade_verifier/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index 268f393e0a..b0ffa9dca7 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -294,8 +294,7 @@ impl TradeVerifier { .with_post_interactions(post_interactions) .with_overrides(override_requests) .build() - .await - .map_err(|e| Error::SimulationFailed(anyhow::anyhow!("{e}")))?; + .await?; // after assembling the state overrides and settlement call data we need to // craft a call that takes the settle call data as an argument. @@ -821,6 +820,8 @@ enum Error { /// Some error caused the simulation to not finish successfully. #[error("quote could not be simulated")] SimulationFailed(#[from] anyhow::Error), + #[error("failed to build the verification simuation")] + FailedToBuildSimulation(#[from] simulator::simulation_builder::BuildError), } /// Spardose gets `needed` plus a 1% headroom, floored at 1 wei so the From 65a4f0f5b372dce7d743f93b9075c085d43ae073 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 13:10:50 +0000 Subject: [PATCH 137/154] prepare jit orders in separate function --- .../src/trade_verifier/mod.rs | 79 ++++++++++--------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/crates/price-estimation/src/trade_verifier/mod.rs b/crates/price-estimation/src/trade_verifier/mod.rs index b0ffa9dca7..8ad638f8fa 100644 --- a/crates/price-estimation/src/trade_verifier/mod.rs +++ b/crates/price-estimation/src/trade_verifier/mod.rs @@ -246,43 +246,7 @@ impl TradeVerifier { }, ); - let jit_orders: Vec = match trade { - TradeKind::Regular(t) => t - .jit_orders - .iter() - .map(|jit_order| { - let order_data = OrderData { - sell_token: jit_order.sell_token, - buy_token: jit_order.buy_token, - receiver: Some(jit_order.receiver), - sell_amount: jit_order.sell_amount, - buy_amount: jit_order.buy_amount, - valid_to: jit_order.valid_to, - app_data: jit_order.app_data, - fee_amount: U256::ZERO, - kind: match &jit_order.side { - Side::Buy => OrderKind::Buy, - Side::Sell => OrderKind::Sell, - }, - partially_fillable: jit_order.partially_fillable, - sell_token_balance: jit_order.sell_token_source, - buy_token_balance: jit_order.buy_token_destination, - }; - let (owner, signature) = recover_jit_order_owner( - jit_order, - &order_data, - &self.simulator.domain_separator(), - )?; - Ok(sim_builder::Order::new(order_data) - .with_signature(owner, signature) - .fill_at( - ExecutionAmount::Explicit(jit_order.executed_amount), - PriceEncoding::LimitPrice, - )) - }) - .collect::>()?, - _ => vec![], - }; + let jit_orders = encode_jit_orders(trade, &self.simulator.domain_separator())?; let eth_call_inputs = self .simulator @@ -780,6 +744,47 @@ pub struct PriceQuery { pub in_amount: NonZeroU256, } +fn encode_jit_orders( + trade: &TradeKind, + domain_separator: &DomainSeparator, +) -> Result> { + let TradeKind::Regular(trade) = trade else { + return Ok(vec![]); + }; + + trade + .jit_orders + .iter() + .map(|jit_order| { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: U256::ZERO, + kind: match &jit_order.side { + Side::Buy => OrderKind::Buy, + Side::Sell => OrderKind::Sell, + }, + partially_fillable: jit_order.partially_fillable, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = + recover_jit_order_owner(jit_order, &order_data, domain_separator)?; + Ok(sim_builder::Order::new(order_data) + .with_signature(owner, signature) + .fill_at( + ExecutionAmount::Explicit(jit_order.executed_amount), + PriceEncoding::LimitPrice, + )) + }) + .collect::>() +} + /// Recovers the owner and signature from a `JitOrder`. fn recover_jit_order_owner( jit_order: &dto::JitOrder, From 5b0499c8dddd0175ca991ebc08d3c21a7278d283 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:17:08 +0000 Subject: [PATCH 138/154] better error --- crates/orderbook/src/orderbook.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index 9f0d8c29f1..23e901ebf2 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -631,11 +631,12 @@ impl Orderbook { return Ok(None); }; - let full_app_data = order - .metadata - .full_app_data - .clone() - .ok_or_else(|| OrderSimulationError::Other(anyhow!("can't find appdata")))?; + let full_app_data = order.metadata.full_app_data.clone().ok_or_else(|| { + OrderSimulationError::Other(anyhow!( + "can't find the full app data for this order in the database, can't simulate the \ + order correctly without that" + )) + })?; let sim = order_simulator .new_simulation_builder() From 4698b86c869f424599f07489d4f95b7777f7cb10 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:18:11 +0000 Subject: [PATCH 139/154] mark fields as required in openapi spec --- crates/orderbook/openapi.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index e91aa496f2..b0deadec01 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -2771,6 +2771,9 @@ components: - owner - signingScheme - signature + - feeAmount + - validTo + - partiallyFillable OrderSimulation: description: > The Tenderly simulation request for an order, along with any From a6ab2f52e3f3a1fb764dd6b977e566a5e5544a65 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:22:49 +0000 Subject: [PATCH 140/154] don't override interactions, remove from order, drop functions --- crates/simulator/src/encoding.rs | 10 ++------- crates/simulator/src/simulation_builder.rs | 26 +++------------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index 122b7a68ae..b40a1ebf1a 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -411,8 +411,6 @@ pub(crate) async fn finish_simulation_builder( // Encode every order as a trade, then collect all their interactions. let mut trades = Vec::with_capacity(n); - let mut all_order_pre: Vec = vec![]; - let mut all_order_post: Vec = vec![]; for (i, (order, exec)) in builder.orders.iter().zip(&executed_amounts).enumerate() { trades.push(encode_trade( &order.data, @@ -422,8 +420,6 @@ pub(crate) async fn finish_simulation_builder( 2 * i + 1, *exec, )); - all_order_pre.extend_from_slice(&order.pre_interactions); - all_order_post.extend_from_slice(&order.post_interactions); } let settlement = EncodedSettlement { @@ -431,11 +427,9 @@ pub(crate) async fn finish_simulation_builder( clearing_prices, trades, interactions: Interactions { - // order pre-hooks run before any additional pre-interactions - pre: encode_interactions(all_order_pre.iter().chain(&builder.pre_interactions)), + pre: encode_interactions(&builder.pre_interactions), main: encode_interactions(&builder.main_interactions), - // additional post-interactions run before order post-hooks - post: encode_interactions(builder.post_interactions.iter().chain(&all_order_post)), + post: encode_interactions(&builder.post_interactions), }, }; diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index e6c19012ed..ef5c4fe4a3 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -149,7 +149,7 @@ impl SimulationBuilder { mut self, interactions: impl IntoIterator, ) -> Self { - self.pre_interactions = interactions.into_iter().collect(); + self.pre_interactions.extend(interactions); self } @@ -157,7 +157,7 @@ impl SimulationBuilder { mut self, interactions: impl IntoIterator, ) -> Self { - self.main_interactions = interactions.into_iter().collect(); + self.main_interactions.extend(interactions); self } @@ -165,7 +165,7 @@ impl SimulationBuilder { mut self, interactions: impl IntoIterator, ) -> Self { - self.post_interactions = interactions.into_iter().collect(); + self.post_interactions.extend(interactions); self } @@ -437,8 +437,6 @@ pub struct Order { pub(crate) data: OrderData, pub(crate) owner: Address, pub(crate) signature: Signature, - pub(crate) pre_interactions: Vec, - pub(crate) post_interactions: Vec, pub(crate) executed_amount: ExecutionAmount, pub(crate) price_encoding: PriceEncoding, } @@ -449,8 +447,6 @@ impl Order { data, owner: Address::ZERO, signature: Signature::default_with(SigningScheme::Eip1271), - pre_interactions: vec![], - post_interactions: vec![], executed_amount: ExecutionAmount::Remaining, price_encoding: PriceEncoding::LimitPrice, } @@ -462,22 +458,6 @@ impl Order { self } - pub fn with_pre_interactions( - mut self, - interactions: impl IntoIterator, - ) -> Self { - self.pre_interactions = interactions.into_iter().collect(); - self - } - - pub fn with_post_interactions( - mut self, - interactions: impl IntoIterator, - ) -> Self { - self.post_interactions = interactions.into_iter().collect(); - self - } - pub fn fill_at(mut self, execution: ExecutionAmount, price: PriceEncoding) -> Self { self.executed_amount = execution; self.price_encoding = price; From 121fbbc6bb913e16dfb594970c0b37f26738e18e Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:23:53 +0000 Subject: [PATCH 141/154] drop unnecessary dependency --- Cargo.lock | 1 - crates/simulator/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9596fd87fb..66ac2aa3c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7776,7 +7776,6 @@ version = "0.1.0" dependencies = [ "alloy-contract", "alloy-eips", - "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", diff --git a/crates/simulator/Cargo.toml b/crates/simulator/Cargo.toml index 5c41b774e0..3218091311 100644 --- a/crates/simulator/Cargo.toml +++ b/crates/simulator/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT OR Apache-2.0" [dependencies] alloy-contract = { workspace = true } alloy-eips = { workspace = true } -alloy-network = { workspace = true } alloy-primitives = { workspace = true } alloy-provider = { workspace = true } alloy-rpc-types = { workspace = true } From 573ae5ea8fc62d353a90fcbbba0dc492b9f014bc Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:58:01 +0000 Subject: [PATCH 142/154] fix comment --- crates/simulator/src/encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/simulator/src/encoding.rs b/crates/simulator/src/encoding.rs index b40a1ebf1a..6c5bdd12b6 100644 --- a/crates/simulator/src/encoding.rs +++ b/crates/simulator/src/encoding.rs @@ -552,7 +552,7 @@ async fn build_final_state_overrides( AccountOverride::default().with_balance(U256::MAX / U256::from(2)), )), AccountOverrideRequest::AuthenticateAsSolver(addr) => { - // GPv2AllowListAuthentication stores `mapping(address => bool) managers` + // GPv2AllowListAuthentication stores `mapping(address => bool) solvers` // at storage slot 1. Solidity mapping key: keccak256(address_padded ++ // slot_padded). // From bd8be29909f527ae6244b237b7258d1fcb1b0857 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:58:45 +0000 Subject: [PATCH 143/154] mark appdata as required as well --- crates/orderbook/openapi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index b0deadec01..225885da69 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -2774,6 +2774,7 @@ components: - feeAmount - validTo - partiallyFillable + - appData OrderSimulation: description: > The Tenderly simulation request for an order, along with any From 14e0f06bc7fb6cfcd429122d99c6cb96132b585d Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Wed, 6 May 2026 14:59:36 +0000 Subject: [PATCH 144/154] comment --- crates/simulator/src/simulation_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index ef5c4fe4a3..4f5a741fb1 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -228,7 +228,7 @@ impl SimulationBuilder { /// Generates 1 interaction executing the given hooks via the trampoline /// contract since executing hooks directly from the settlement contract - /// context would give them elevated priviliges that puts funds at risk. + /// context would give them elevated privileges that put funds at risk. fn encode_hooks(&self, hooks: &[app_data::Hook]) -> Vec { if hooks.is_empty() { return vec![]; From 5679bc0894d64b1a46d199d6dde414b781a82d18 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 6 May 2026 16:51:31 +0100 Subject: [PATCH 145/154] Address PR review feedback - Rename EIP-1271-specific identifiers to order-simulation since the underlying logic is not 1271-specific. Affects the trait (Eip1271Simulating -> OrderSimulating), error type, mode enum, bundle struct, metrics, config fields (eip1271_simulation_{mode, timeout} -> order_simulation_{mode, timeout}), file name (eip1271_simulation.rs -> order_simulation.rs), and the API error code (Eip1271SimulationFailed -> OrderSimulationFailed). Per MartinquaXD's review. - Make Tenderly's simulate_and_share lazy in the order-simulation adapter. Previously every successful simulation triggered a Tenderly API call whose URL was discarded. Now the local eth_call runs first and Tenderly is only consulted when there is a revert to attach a diagnostic URL to the error. Adds SettlementSimulator:: tenderly() so the adapter can reach the configured Tenderly handle. - Move the maintainer note about why the Eip1271Simulating trait lives in `shared` rather than `simulator` from a doc comment to a plain `//` comment. The trait's user-facing summary stays as rustdoc. - Inline the single-use build_preview_order_for_sim helper at its one call site. - Switch the OrderSimulationConfig parse-test fixtures from hex "0x1000000" to decimal "16777216" to match the rest of the gas-limit configuration in the codebase. - Rephrase canonicalise_app_data's doc to credit serde_json::Value's BTreeMap backing for the alphabetical ordering rather than the input being pre-sorted. - Rename the test no_simulator_configured_preserves_existing_behaviour to no_simulator_configured_returns_invalid_eip1271_signature_on_invalid_signature so the assertion is named after the actual behaviour, not "existing behaviour" which rots. - Replace Address::from([0xcf; 20]) with Address::repeat_byte(0xcf) to match Address::repeat_byte(0xef) on the sibling line. - Tighten the run_eip1271_checks rustdoc with doclinks and a clearer true/false split for eip1271_skip_creation_validation. - Add TestDefault for OrderSimulationConfig and use destructuring in the e2e enforce-mode helper (replaces the .as_mut().expect() chain). --- crates/configs/src/orderbook/mod.rs | 67 +++--- .../tests/e2e/eip1271_creation_simulation.rs | 26 ++- crates/orderbook/src/api/post_order.rs | 2 +- crates/orderbook/src/eip1271_simulation.rs | 64 ------ crates/orderbook/src/lib.rs | 2 +- crates/orderbook/src/order_simulation.rs | 71 ++++++ crates/orderbook/src/run.rs | 26 +-- crates/shared/src/order_validation.rs | 209 ++++++++---------- crates/simulator/src/simulation_builder.rs | 4 + crates/simulator/tests/aave_replay.rs | 10 +- 10 files changed, 238 insertions(+), 243 deletions(-) delete mode 100644 crates/orderbook/src/eip1271_simulation.rs create mode 100644 crates/orderbook/src/order_simulation.rs diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 0f1139436f..2d6546b13e 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -61,29 +61,26 @@ pub struct OrderSimulationConfig { #[serde(default)] pub tenderly: Option, - /// Mode for the EIP-1271 order simulation. + /// Mode for the order creation simulation. #[serde(default)] - pub eip1271_simulation_mode: Eip1271SimulationMode, - - /// Per-call timeout for the EIP-1271 order simulation. - #[serde( - default = "default_eip1271_simulation_timeout", - with = "humantime_serde" - )] - pub eip1271_simulation_timeout: Duration, + pub order_simulation_mode: OrderSimulationMode, + + /// Per-call timeout for the order creation simulation. + #[serde(default = "default_order_simulation_timeout", with = "humantime_serde")] + pub order_simulation_timeout: Duration, } -/// Mode for the EIP-1271 order simulation at order creation. +/// Mode for the order creation simulation. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] -pub enum Eip1271SimulationMode { +pub enum OrderSimulationMode { Shadow, Enforce, #[default] Disabled, } -fn default_eip1271_simulation_timeout() -> Duration { +fn default_order_simulation_timeout() -> Duration { Duration::from_secs(2) } @@ -197,6 +194,17 @@ pub mod test_util { std::path::Path, }; + impl TestDefault for OrderSimulationConfig { + fn test_default() -> Self { + Self { + gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), + tenderly: None, + order_simulation_mode: Default::default(), + order_simulation_timeout: std::time::Duration::from_secs(2), + } + } + } + impl Configuration { pub async fn to_path>(&self, path: P) -> anyhow::Result<()> { Ok(tokio::fs::write(path, toml::to_string_pretty(self)?).await?) @@ -251,12 +259,7 @@ pub mod test_util { ..TestDefault::test_default() }, // Enable order simulation for testing - order_simulation: Some(OrderSimulationConfig { - gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), - tenderly: None, - eip1271_simulation_mode: Default::default(), - eip1271_simulation_timeout: std::time::Duration::from_secs(2), - }), + order_simulation: Some(TestDefault::test_default()), hide_competition_before_deadline: false, } } @@ -469,41 +472,41 @@ mod tests { #[test] fn parses_simulation_mode_default() { - let toml = r#"gas-limit = "0x1000000""#; + let toml = r#"gas-limit = "16777216""#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); - assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(2)); + assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Disabled); + assert_eq!(cfg.order_simulation_timeout, Duration::from_secs(2)); } #[test] fn parses_simulation_mode_enforce() { let toml = r#" - gas-limit = "0x1000000" - eip1271-simulation-mode = "enforce" - eip1271-simulation-timeout = "5s" + gas-limit = "16777216" + order-simulation-mode = "enforce" + order-simulation-timeout = "5s" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Enforce); - assert_eq!(cfg.eip1271_simulation_timeout, Duration::from_secs(5)); + assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Enforce); + assert_eq!(cfg.order_simulation_timeout, Duration::from_secs(5)); } #[test] fn parses_simulation_mode_shadow() { let toml = r#" - gas-limit = "0x1000000" - eip1271-simulation-mode = "shadow" + gas-limit = "16777216" + order-simulation-mode = "shadow" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Shadow); + assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Shadow); } #[test] fn parses_simulation_mode_disabled() { let toml = r#" - gas-limit = "0x1000000" - eip1271-simulation-mode = "disabled" + gas-limit = "16777216" + order-simulation-mode = "disabled" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.eip1271_simulation_mode, Eip1271SimulationMode::Disabled); + assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Disabled); } } diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 4046a8a9d4..7d4f280a9f 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -2,9 +2,9 @@ //! //! - Negative: a Safe-signed order whose `app_data.protocol.wrappers` points at //! a custom always-revert wrapper. With the orderbook in -//! `Eip1271SimulationMode::Enforce`, the simulation drives `settle()` through +//! `OrderSimulationMode::Enforce`, the simulation drives `settle()` through //! the wrapper, the wrapper reverts, and the API rejects with HTTP 400 -//! `Eip1271SimulationFailed`. A wrapper is used rather than a buggy pre-hook +//! `OrderSimulationFailed`. A wrapper is used rather than a buggy pre-hook //! because `HooksTrampoline.execute` deliberately swallows hook reverts, so a //! buggy hook would not surface as a simulation failure. //! - Positive: a Safe-signed order with empty app_data is accepted, proving the @@ -16,7 +16,10 @@ use { providers::Provider, rpc::types::TransactionRequest, }, - configs::{orderbook::Eip1271SimulationMode, test_util::TestDefault}, + configs::{ + orderbook::{Configuration, OrderSimulationConfig, OrderSimulationMode}, + test_util::TestDefault, + }, e2e::setup::{MintableToken, OnchainComponents, Services, run_test, safe::Safe}, model::order::{OrderCreation, OrderCreationAppData, OrderKind}, number::units::EthUnit, @@ -73,8 +76,8 @@ async fn rejects_when_wrapper_reverts(web3: Web3) { let (status, body) = services.create_order(&order).await.unwrap_err(); assert_eq!(status, StatusCode::BAD_REQUEST, "body: {body}"); assert!( - body.contains("Eip1271SimulationFailed"), - "expected Eip1271SimulationFailed in body, got: {body}", + body.contains("OrderSimulationFailed"), + "expected OrderSimulationFailed in body, got: {body}", ); } @@ -135,12 +138,13 @@ async fn start_services_in_enforce_mode<'a>( onchain: &'a OnchainComponents, solver: e2e::setup::onchain_components::TestAccount, ) -> Services<'a> { - let mut orderbook_config = configs::orderbook::Configuration::test_default(); - orderbook_config - .order_simulation - .as_mut() - .expect("test_default enables order_simulation") - .eip1271_simulation_mode = Eip1271SimulationMode::Enforce; + let orderbook_config = Configuration { + order_simulation: Some(OrderSimulationConfig { + order_simulation_mode: OrderSimulationMode::Enforce, + ..TestDefault::test_default() + }), + ..Configuration::test_default() + }; let services = Services::new(onchain).await; services diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index 5965bfd4e0..3c63507428 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -190,7 +190,7 @@ impl IntoResponse for ValidationErrorWrapper { .into_response(), ValidationError::SimulationFailed(reason) => ( StatusCode::BAD_REQUEST, - error("Eip1271SimulationFailed", reason), + error("OrderSimulationFailed", reason), ) .into_response(), ValidationError::InsufficientBalance => ( diff --git a/crates/orderbook/src/eip1271_simulation.rs b/crates/orderbook/src/eip1271_simulation.rs deleted file mode 100644 index 1f18af9346..0000000000 --- a/crates/orderbook/src/eip1271_simulation.rs +++ /dev/null @@ -1,64 +0,0 @@ -use { - anyhow::anyhow, - async_trait::async_trait, - model::order::Order, - shared::order_validation::{Eip1271Simulating, Eip1271SimulationError}, - simulator::simulation_builder::{ - self, - Block, - ExecutionAmount, - PriceEncoding, - SettlementSimulator, - Solver, - }, -}; - -/// Drives `SettlementSimulator` to run a full-order simulation for an -/// EIP-1271 order at creation time. Used by the orderbook's signature + -/// simulation matrix. -pub struct OrderSimulatorAdapter { - inner: SettlementSimulator, -} - -impl OrderSimulatorAdapter { - pub fn new(inner: SettlementSimulator) -> Self { - Self { inner } - } -} - -#[async_trait] -impl Eip1271Simulating for OrderSimulatorAdapter { - async fn simulate( - &self, - order: &Order, - full_app_data: String, - ) -> Result<(), Eip1271SimulationError> { - let inputs = self - .inner - .new_simulation_builder() - .with_orders([simulation_builder::Order::new(order.data) - .with_signature(order.metadata.owner, order.signature.clone()) - .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) - .parameters_from_app_data(&full_app_data) - .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("parse app data")))? - .from_solver(Solver::Fake(None)) - .provide_sufficient_buy_tokens() - .at_block(Block::Latest) - .build() - .await - .map_err(|err| Eip1271SimulationError::Infra(anyhow!(err).context("build")))?; - - let report = inputs - .simulate_with_tenderly_report() - .await - .map_err(|err| Eip1271SimulationError::Infra(err.context("simulate")))?; - - match report.error { - Some(reason) => Err(Eip1271SimulationError::Reverted { - reason, - tenderly_url: report.tenderly_url, - }), - None => Ok(()), - } - } -} diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index fd51fe22e4..182d69d261 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -3,9 +3,9 @@ pub mod app_data; pub mod arguments; pub mod database; pub mod dto; -mod eip1271_simulation; mod ipfs; mod ipfs_app_data; +mod order_simulation; pub mod orderbook; mod quoter; pub mod run; diff --git a/crates/orderbook/src/order_simulation.rs b/crates/orderbook/src/order_simulation.rs new file mode 100644 index 0000000000..62a87d2be9 --- /dev/null +++ b/crates/orderbook/src/order_simulation.rs @@ -0,0 +1,71 @@ +use { + anyhow::anyhow, + async_trait::async_trait, + model::order::Order, + shared::order_validation::{OrderSimulating, OrderSimulationError}, + simulator::simulation_builder::{ + self, + Block, + ExecutionAmount, + PriceEncoding, + SettlementSimulator, + Solver, + }, +}; + +/// Drives `SettlementSimulator` to run a full-order simulation at order +/// creation time, including pre/post hooks, swap, and any wrapper chain. +pub struct OrderSimulatorAdapter { + inner: SettlementSimulator, +} + +impl OrderSimulatorAdapter { + pub fn new(inner: SettlementSimulator) -> Self { + Self { inner } + } +} + +#[async_trait] +impl OrderSimulating for OrderSimulatorAdapter { + async fn simulate( + &self, + order: &Order, + full_app_data: String, + ) -> Result<(), OrderSimulationError> { + let inputs = self + .inner + .new_simulation_builder() + .with_orders([simulation_builder::Order::new(order.data) + .with_signature(order.metadata.owner, order.signature.clone()) + .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) + .parameters_from_app_data(&full_app_data) + .map_err(|err| OrderSimulationError::Infra(anyhow!(err).context("parse app data")))? + .from_solver(Solver::Fake(None)) + .provide_sufficient_buy_tokens() + .at_block(Block::Latest) + .build() + .await + .map_err(|err| OrderSimulationError::Infra(anyhow!(err).context("build")))?; + + // Capture the Tenderly handle and the diagnostic request before + // consuming `inputs` with `simulate()`. The Tenderly call is only + // dispatched on revert, since the URL is only useful for diagnostics + // and most simulations succeed. + let tenderly = inputs.simulator.tenderly(); + let tenderly_request = inputs.to_tenderly_request().ok(); + + match inputs.simulate().await { + Ok(_) => Ok(()), + Err(err) => { + let tenderly_url = match (tenderly, tenderly_request) { + (Some(api), Some(req)) => api.simulate_and_share(req).await.ok(), + _ => None, + }; + Err(OrderSimulationError::Reverted { + reason: err.to_string(), + tenderly_url, + }) + } + } + } +} diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 1d61148293..d0655d6cb5 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -42,8 +42,8 @@ use { shared::{ order_quoting::{self, OrderQuoter}, order_validation::{ - Eip1271SimulationMode, - Eip1271Simulator, + OrderSimulationMode, + OrderSimulator, OrderValidPeriodConfiguration, OrderValidator, }, @@ -419,25 +419,23 @@ pub async fn run(config: Configuration) { None => None, }; - let eip1271_simulator = config + let validator_simulator = config .order_simulation .as_ref() .zip(order_simulator.clone()) .and_then(|(sim_config, simulator)| { - let mode = match sim_config.eip1271_simulation_mode { - configs::orderbook::Eip1271SimulationMode::Shadow => Eip1271SimulationMode::Shadow, - configs::orderbook::Eip1271SimulationMode::Enforce => { - Eip1271SimulationMode::Enforce - } - configs::orderbook::Eip1271SimulationMode::Disabled => return None, + let mode = match sim_config.order_simulation_mode { + configs::orderbook::OrderSimulationMode::Shadow => OrderSimulationMode::Shadow, + configs::orderbook::OrderSimulationMode::Enforce => OrderSimulationMode::Enforce, + configs::orderbook::OrderSimulationMode::Disabled => return None, }; - let simulator: Arc = Arc::new( - crate::eip1271_simulation::OrderSimulatorAdapter::new(simulator), + let simulator: Arc = Arc::new( + crate::order_simulation::OrderSimulatorAdapter::new(simulator), ); - Some(Eip1271Simulator { + Some(OrderSimulator { simulator, mode, - timeout: sim_config.eip1271_simulation_timeout, + timeout: sim_config.order_simulation_timeout, }) }); @@ -455,7 +453,7 @@ pub async fn run(config: Configuration) { optimal_quoter.clone(), balance_fetcher, signature_validator, - eip1271_simulator, + validator_simulator, Arc::new(postgres_write.clone()), config.order_validation.max_limit_orders_per_user, code_fetcher, diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 54a557bf00..654fac7384 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -48,9 +48,9 @@ use { tracing::instrument, }; -/// Outcome of the EIP-1271 order simulation. +/// Outcome of the order creation simulation. #[derive(Debug)] -pub enum Eip1271SimulationError { +pub enum OrderSimulationError { /// The simulation ran and the transaction reverted. `reason` is the /// revert string returned by the EVM (or a Tenderly reason string). Reverted { @@ -63,29 +63,27 @@ pub enum Eip1271SimulationError { Infra(anyhow::Error), } -/// Simulates an EIP-1271 order's pre-hooks, swap, and post-hooks against -/// the chain. -/// -/// Defined here rather than in `crates/simulator` because `OrderValidator` -/// cannot depend on `orderbook`, where the concrete implementation lives. -/// To be removed once the `simulator` crate's API can be depended on -/// directly. +// Defined here rather than in `crates/simulator` because `OrderValidator` +// cannot depend on `orderbook`, where the concrete implementation lives. +// To be removed once the `simulator` crate's API can be depended on +// directly. +/// Simulates an order's pre-hooks, swap, and post-hooks against the chain. #[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] -pub trait Eip1271Simulating: Send + Sync { +pub trait OrderSimulating: Send + Sync { async fn simulate( &self, order: &Order, full_app_data: String, - ) -> Result<(), Eip1271SimulationError>; + ) -> Result<(), OrderSimulationError>; } -/// Mode controlling whether the EIP-1271 order simulation can reject orders. -/// The operational default lives in `configs::orderbook::Eip1271SimulationMode` -/// (currently `Disabled`); this enum only represents the on-path states +/// Mode controlling whether the order creation simulation can reject orders. +/// The operational default lives in `configs::orderbook::OrderSimulationMode` +/// (currently `Disabled`). This enum only represents the on-path states /// `OrderValidator` actually executes. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Eip1271SimulationMode { +pub enum OrderSimulationMode { /// Log disagreements, emit metrics. Never reject. Shadow, /// If the signature check passes but the order simulation fails, reject @@ -98,28 +96,28 @@ pub enum Eip1271SimulationMode { /// check and decides, based on the configured mode, whether a failure /// should reject the order. #[derive(Clone)] -pub struct Eip1271Simulator { - pub simulator: Arc, - pub mode: Eip1271SimulationMode, +pub struct OrderSimulator { + pub simulator: Arc, + pub mode: OrderSimulationMode, pub timeout: Duration, } #[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] -#[metric(subsystem = "eip1271_simulation")] -struct Eip1271SimulationMetrics { +#[metric(subsystem = "order_simulation")] +struct OrderSimulationMetrics { /// Counts each (signature-check, simulation) outcome pair. The signature /// axis takes `pass | fail | infra | skipped`; the simulation axis /// takes `pass | fail | infra`. #[metric(labels("signature", "simulation"))] total: prometheus::IntCounterVec, - /// Time spent in the EIP-1271 order simulation. + /// Time spent in the order creation simulation. simulation_time: prometheus::Histogram, } -impl Eip1271SimulationMetrics { +impl OrderSimulationMetrics { fn get() -> &'static Self { Self::instance(observe::metrics::get_storage_registry()) - .expect("unexpected error getting Eip1271SimulationMetrics instance") + .expect("unexpected error getting OrderSimulationMetrics instance") } } @@ -176,25 +174,23 @@ fn classify_signature(res: &Result) -> SignatureO /// `simulation_time` histogram, and folds the timeout / revert / infra error /// cases into a single `SimulationOutcome`. async fn timed_simulation( - sim: &dyn Eip1271Simulating, + sim: &dyn OrderSimulating, order: &Order, full_app_data: String, timeout: Duration, ) -> SimulationOutcome { - let _timer = Eip1271SimulationMetrics::get() - .simulation_time - .start_timer(); + let _timer = OrderSimulationMetrics::get().simulation_time.start_timer(); match tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await { Ok(Ok(())) => SimulationOutcome::Pass, - Ok(Err(Eip1271SimulationError::Reverted { + Ok(Err(OrderSimulationError::Reverted { reason, tenderly_url, })) => SimulationOutcome::Fail { reason, tenderly_url, }, - Ok(Err(Eip1271SimulationError::Infra(err))) => SimulationOutcome::Infra(err), - Err(_) => SimulationOutcome::Infra(anyhow!("eip1271 simulation timeout")), + Ok(Err(OrderSimulationError::Infra(err))) => SimulationOutcome::Infra(err), + Err(_) => SimulationOutcome::Infra(anyhow!("order simulation timeout")), } } @@ -203,7 +199,7 @@ fn record_simulation_outcome( simulation: &SimulationOutcome, order_uid: OrderUid, ) { - Eip1271SimulationMetrics::get() + OrderSimulationMetrics::get() .total .with_label_values(&[signature.label(), simulation.label()]) .inc(); @@ -221,23 +217,23 @@ fn record_simulation_outcome( simulation = simulation.label(), ?reason, ?tenderly_url, - "eip1271 simulation disagreement", + "order simulation disagreement", ), (SignatureOutcome::Fail, SimulationOutcome::Pass) => tracing::warn!( ?order_uid, signature = signature.label(), simulation = simulation.label(), - "eip1271 simulation disagreement", + "order simulation disagreement", ), (_, SimulationOutcome::Infra(err)) => { - tracing::warn!(?order_uid, ?err, "eip1271 simulation infra error") + tracing::warn!(?order_uid, ?err, "order simulation infra error") } _ => {} } } -async fn run_eip1271_simulation_only( - config: &Eip1271Simulator, +async fn run_order_simulation_only( + config: &OrderSimulator, preview_order: &Order, full_app_data: String, ) { @@ -248,7 +244,7 @@ async fn run_eip1271_simulation_only( config.timeout, ) .await; - Eip1271SimulationMetrics::get() + OrderSimulationMetrics::get() .total .with_label_values(&[SignatureOutcome::Skipped.label(), outcome.label()]) .inc(); @@ -260,36 +256,17 @@ async fn run_eip1271_simulation_only( order_uid = %preview_order.metadata.uid, ?reason, ?tenderly_url, - "eip1271 simulation (signature check skipped)", + "order simulation (signature check skipped)", ), SimulationOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, ?err, - "eip1271 simulation infra error (signature check skipped)", + "order simulation infra error (signature check skipped)", ), SimulationOutcome::Pass => {} } } -fn build_preview_order_for_sim( - data: &OrderData, - interactions: &Interactions, - owner: Address, - uid: OrderUid, - signature: Signature, -) -> Order { - Order { - metadata: OrderMetadata { - owner, - uid, - ..Default::default() - }, - data: *data, - signature, - interactions: interactions.clone(), - } -} - #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -493,7 +470,7 @@ pub struct OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_simulator: Option, + order_simulator: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, pub code_fetcher: Arc, @@ -564,7 +541,7 @@ impl OrderValidator { quoter: Arc, balance_fetcher: Arc, signature_validator: Arc, - eip1271_simulator: Option, + order_simulator: Option, limit_order_counter: Arc, max_limit_orders_per_user: u64, code_fetcher: Arc, @@ -582,7 +559,7 @@ impl OrderValidator { quoter, balance_fetcher, signature_validator, - eip1271_simulator, + order_simulator, limit_order_counter, max_limit_orders_per_user, code_fetcher, @@ -624,13 +601,16 @@ impl OrderValidator { amount: loan.amount, }), ); - let preview_order = build_preview_order_for_sim( - data, - &app_data.interactions, - owner, - uid, - order.signature.clone(), - ); + let preview_order = Order { + metadata: OrderMetadata { + owner, + uid, + ..Default::default() + }, + data: *data, + signature: order.signature.clone(), + interactions: app_data.interactions.clone(), + }; let full_app_data = app_data.inner.document.clone(); self.run_eip1271_checks(check, &preview_order, full_app_data, hash) .await @@ -638,14 +618,15 @@ impl OrderValidator { /// Entry point for the EIP-1271 block of `validate_and_construct_order`. /// - /// Two paths, depending on the `eip1271_skip_creation_validation` flag: + /// When the [`OrderValidator::eip1271_skip_creation_validation`] flag is: /// - /// - **Skipped**: the cheap `isValidSignature` check is bypassed by the + /// - `true`: the cheap `isValidSignature` check is bypassed by the /// operator, and we return a `verification_gas_limit` of `0` (no gas was /// spent on-chain verifying the signature). If the optional - /// `Eip1271Simulator` is configured, the full simulation still runs for + /// [`OrderSimulator`] is configured, the full simulation still runs for /// observability only and can never reject. - /// - **Not skipped**: delegates to `run_eip1271_with_signature_check`. + /// - `false`: delegates to + /// [`OrderValidator::run_eip1271_with_signature_check`]. async fn run_eip1271_checks( &self, check: SignatureCheck, @@ -654,8 +635,8 @@ impl OrderValidator { hash: B256, ) -> Result { if self.eip1271_skip_creation_validation { - if let Some(config) = &self.eip1271_simulator { - run_eip1271_simulation_only(config, preview_order, full_app_data).await; + if let Some(config) = &self.order_simulator { + run_order_simulation_only(config, preview_order, full_app_data).await; } return Ok(0u64); } @@ -685,7 +666,7 @@ impl OrderValidator { .signature_validator .validate_signature_and_get_additional_gas(check); - let Some(config) = &self.eip1271_simulator else { + let Some(config) = &self.order_simulator else { return signature_fut.await.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), SignatureValidationError::Other(err) => ValidationError::Other(err), @@ -709,7 +690,7 @@ impl OrderValidator { ); match (signature_res, &simulation_outcome, config.mode) { - (Ok(_gas), SimulationOutcome::Fail { reason, .. }, Eip1271SimulationMode::Enforce) => { + (Ok(_gas), SimulationOutcome::Fail { reason, .. }, OrderSimulationMode::Enforce) => { Err(ValidationError::SimulationFailed(reason.clone())) } (Ok(gas), _, _) => Ok(gas), @@ -1429,7 +1410,7 @@ mod tests { signature_validator::MockSignatureValidating, }; - const DEFAULT_EIP1271_SIM_TIMEOUT: Duration = Duration::from_secs(2); + const DEFAULT_ORDER_SIM_TIMEOUT: Duration = Duration::from_secs(2); #[tokio::test] async fn pre_validate_err() { @@ -1452,7 +1433,7 @@ mod tests { false, DenyListedTokens::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -1609,7 +1590,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -1691,7 +1672,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2020,7 +2001,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2094,7 +2075,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2156,7 +2137,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2211,7 +2192,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2270,7 +2251,7 @@ mod tests { false, deny_listed_tokens, HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2332,7 +2313,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2393,7 +2374,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2553,7 +2534,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -2966,7 +2947,7 @@ mod tests { false, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -3029,20 +3010,17 @@ mod tests { } } - fn simulator_with_mode( - sim: MockEip1271Simulating, - mode: Eip1271SimulationMode, - ) -> Eip1271Simulator { - Eip1271Simulator { + fn simulator_with_mode(sim: MockOrderSimulating, mode: OrderSimulationMode) -> OrderSimulator { + OrderSimulator { simulator: Arc::new(sim), mode, - timeout: DEFAULT_EIP1271_SIM_TIMEOUT, + timeout: DEFAULT_ORDER_SIM_TIMEOUT, } } fn build_1271_validator( signature_validator: MockSignatureValidating, - eip1271_simulator: Option, + order_simulator: Option, eip1271_skip_creation_validation: bool, ) -> OrderValidator { // The quote lookup, balance fetch, and limit-order count are off the @@ -3067,7 +3045,7 @@ mod tests { eip1271_skip_creation_validation, Default::default(), HooksTrampoline::Instance::new( - Address::from([0xcf; 20]), + Address::repeat_byte(0xcf), ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), @@ -3075,7 +3053,7 @@ mod tests { Arc::new(order_quoter), Arc::new(balance_fetcher), Arc::new(signature_validator), - eip1271_simulator, + order_simulator, Arc::new(limit_order_counter), 0, Arc::new(MockCodeFetching::new()), @@ -3089,10 +3067,10 @@ mod tests { /// /// Only the `(signature Pass, simulation Fail, Enforce)` cell changes /// behaviour relative to today. Every other cell must match the - /// existing signature-only behaviour. + /// signature-only behaviour from before order simulation was added. #[tokio::test] async fn signature_and_simulation_outcome_matrix() { - use Eip1271SimulationMode::{Enforce, Shadow}; + use OrderSimulationMode::{Enforce, Shadow}; #[derive(Copy, Clone, Debug)] enum Sig { @@ -3111,7 +3089,7 @@ mod tests { SimulationFailed, } - let cases: &[(Sig, Sim, Eip1271SimulationMode, Expected)] = &[ + let cases: &[(Sig, Sim, OrderSimulationMode, Expected)] = &[ (Sig::Pass, Sim::Pass, Shadow, Expected::Accepted), (Sig::Pass, Sim::Reverted, Shadow, Expected::Accepted), (Sig::Invalid, Sim::Pass, Shadow, Expected::InvalidSignature), @@ -3146,11 +3124,11 @@ mod tests { Sig::Pass => Ok(0u64), Sig::Invalid => Err(SignatureValidationError::Invalid), }); - let mut sim = MockEip1271Simulating::new(); + let mut sim = MockOrderSimulating::new(); sim.expect_simulate() .returning(move |_, _| match simulation { Sim::Pass => Ok(()), - Sim::Reverted => Err(Eip1271SimulationError::Reverted { + Sim::Reverted => Err(OrderSimulationError::Reverted { reason: "hook reverted".into(), tenderly_url: None, }), @@ -3184,23 +3162,20 @@ mod tests { #[tokio::test] async fn simulation_infra_error_is_fail_open_in_both_modes() { - for mode in [ - Eip1271SimulationMode::Shadow, - Eip1271SimulationMode::Enforce, - ] { + for mode in [OrderSimulationMode::Shadow, OrderSimulationMode::Enforce] { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .returning(|_| Ok(0u64)); - let mut sim = MockEip1271Simulating::new(); + let mut sim = MockOrderSimulating::new(); sim.expect_simulate() - .returning(|_, _| Err(Eip1271SimulationError::Infra(anyhow!("RPC down")))); + .returning(|_, _| Err(OrderSimulationError::Infra(anyhow!("RPC down")))); let validator = build_1271_validator( signature_validator, - Some(Eip1271Simulator { + Some(OrderSimulator { simulator: Arc::new(sim), mode, - timeout: DEFAULT_EIP1271_SIM_TIMEOUT, + timeout: DEFAULT_ORDER_SIM_TIMEOUT, }), false, ); @@ -3227,16 +3202,16 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); - let mut sim = MockEip1271Simulating::new(); + let mut sim = MockOrderSimulating::new(); sim.expect_simulate().returning(|_, _| { - Err(Eip1271SimulationError::Reverted { + Err(OrderSimulationError::Reverted { reason: "x".into(), tenderly_url: None, }) }); let validator = build_1271_validator( signature_validator, - Some(simulator_with_mode(sim, Eip1271SimulationMode::Enforce)), + Some(simulator_with_mode(sim, OrderSimulationMode::Enforce)), true, ); let result = validator @@ -3259,11 +3234,11 @@ mod tests { signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); - let mut sim = MockEip1271Simulating::new(); + let mut sim = MockOrderSimulating::new(); sim.expect_simulate().times(0); let validator = build_1271_validator( signature_validator, - Some(simulator_with_mode(sim, Eip1271SimulationMode::Enforce)), + Some(simulator_with_mode(sim, OrderSimulationMode::Enforce)), false, ); @@ -3285,7 +3260,7 @@ mod tests { } #[tokio::test] - async fn no_simulator_configured_preserves_existing_behaviour() { + async fn no_simulator_configured_returns_invalid_eip1271_signature_on_invalid_signature() { let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 4f5a741fb1..747b2334cc 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -93,6 +93,10 @@ impl SettlementSimulator { self.0.authenticator } + pub fn tenderly(&self) -> Option> { + self.0.tenderly.clone() + } + pub fn new_simulation_builder(&self) -> SimulationBuilder { SimulationBuilder { simulator: self.clone(), diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index f4b4f023d0..e07072c3da 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -58,9 +58,13 @@ const APP_DATA: &str = r#"{ "version": "1.14.0" }"#; -/// Returns `app_data` minified with keys sorted alphabetically. The output -/// matches the signed production bytes byte-for-byte because that payload -/// is already alphabetically keyed at every level. +/// Returns `app_data` minified with object keys sorted alphabetically. +/// +/// The sort comes from `serde_json::Value::Object`'s `BTreeMap` backing, which +/// applies whenever the `preserve_order` feature is not enabled (it isn't in +/// this workspace). The signed production payload happens to already be +/// alphabetically keyed at every level, so the output matches it byte for +/// byte. fn canonicalise_app_data(app_data: &str) -> String { let value: serde_json::Value = serde_json::from_str(app_data).expect("APP_DATA must be valid JSON"); From 888e0b1db7eaef4f3a4bd1abf1f3126e775443e3 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 6 May 2026 17:19:13 +0100 Subject: [PATCH 146/154] Move OrderSimulating + adapter into the simulator crate The trait used to live in `shared::order_validation` because `OrderValidator` could not depend on `orderbook` (where the concrete adapter lived). The `simulator` crate's API is stable now, so: - Add `simulator` to `shared`'s `[dependencies]` (and to `[dev-dependencies]` with the `test-util` feature so `MockOrderSimulating` is available to validator tests). - Move `OrderSimulating` trait, `OrderSimulationError` enum, and `OrderSimulatorAdapter` into a new `simulator::order_simulation` module. - Re-export the trait + error from `simulator` and import them in `shared::order_validation` instead of redefining. - Delete `crates/orderbook/src/order_simulation.rs` and its module declaration; `orderbook::run` now constructs the adapter directly from `simulator::order_simulation::OrderSimulatorAdapter`. - The mockall attribute on the trait switches from `cfg(test)` to `any(test, feature = "test-util")` so downstream crates can use `MockOrderSimulating`. The `OrderSimulator` bundle (simulator + mode + timeout) and `OrderSimulationMode` stay in `shared` since they describe the validator's behaviour, not the simulation itself. --- Cargo.lock | 1 + crates/orderbook/src/lib.rs | 1 - crates/orderbook/src/run.rs | 6 ++-- crates/shared/Cargo.toml | 2 ++ crates/shared/src/order_validation.rs | 32 ++--------------- crates/simulator/src/lib.rs | 1 + .../src/order_simulation.rs | 36 +++++++++++++++---- 7 files changed, 39 insertions(+), 40 deletions(-) rename crates/{orderbook => simulator}/src/order_simulation.rs (71%) diff --git a/Cargo.lock b/Cargo.lock index 66ac2aa3c5..3a239536ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7704,6 +7704,7 @@ dependencies = [ "serde_json", "serde_with", "signature-validator", + "simulator", "strum", "testlib", "thiserror 1.0.69", diff --git a/crates/orderbook/src/lib.rs b/crates/orderbook/src/lib.rs index 182d69d261..2b380334fa 100644 --- a/crates/orderbook/src/lib.rs +++ b/crates/orderbook/src/lib.rs @@ -5,7 +5,6 @@ pub mod database; pub mod dto; mod ipfs; mod ipfs_app_data; -mod order_simulation; pub mod orderbook; mod quoter; pub mod run; diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index d0655d6cb5..f4038caea3 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -423,14 +423,14 @@ pub async fn run(config: Configuration) { .order_simulation .as_ref() .zip(order_simulator.clone()) - .and_then(|(sim_config, simulator)| { + .and_then(|(sim_config, settlement_simulator)| { let mode = match sim_config.order_simulation_mode { configs::orderbook::OrderSimulationMode::Shadow => OrderSimulationMode::Shadow, configs::orderbook::OrderSimulationMode::Enforce => OrderSimulationMode::Enforce, configs::orderbook::OrderSimulationMode::Disabled => return None, }; - let simulator: Arc = Arc::new( - crate::order_simulation::OrderSimulatorAdapter::new(simulator), + let simulator: Arc = Arc::new( + simulator::order_simulation::OrderSimulatorAdapter::new(settlement_simulator), ); Some(OrderSimulator { simulator, diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index ab8b09f759..549a4d9a60 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -56,6 +56,7 @@ serde = { workspace = true } serde_json = { workspace = true } serde_with = { workspace = true } signature-validator = { workspace = true } +simulator = { workspace = true } strum = { workspace = true } thiserror = { workspace = true } token-info = { workspace = true } @@ -75,6 +76,7 @@ mockall = { workspace = true } price-estimation = { workspace = true, features = ["test-util"] } regex = { workspace = true } signature-validator = { workspace = true, features = ["test-util"] } +simulator = { workspace = true, features = ["test-util"] } testlib = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } toml = { workspace = true } diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 654fac7384..b07f0e480e 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -44,40 +44,11 @@ use { trade_verifier::code_fetching::CodeFetching, }, signature_validator::{SignatureCheck, SignatureValidating, SignatureValidationError}, + simulator::order_simulation::{OrderSimulating, OrderSimulationError}, std::{sync::Arc, time::Duration}, tracing::instrument, }; -/// Outcome of the order creation simulation. -#[derive(Debug)] -pub enum OrderSimulationError { - /// The simulation ran and the transaction reverted. `reason` is the - /// revert string returned by the EVM (or a Tenderly reason string). - Reverted { - reason: String, - tenderly_url: Option, - }, - /// The simulation could not run (RPC failure, Tenderly error, malformed - /// input, timeout). Treated as fail-open in both Shadow and Enforce - /// modes. - Infra(anyhow::Error), -} - -// Defined here rather than in `crates/simulator` because `OrderValidator` -// cannot depend on `orderbook`, where the concrete implementation lives. -// To be removed once the `simulator` crate's API can be depended on -// directly. -/// Simulates an order's pre-hooks, swap, and post-hooks against the chain. -#[cfg_attr(test, mockall::automock)] -#[async_trait::async_trait] -pub trait OrderSimulating: Send + Sync { - async fn simulate( - &self, - order: &Order, - full_app_data: String, - ) -> Result<(), OrderSimulationError>; -} - /// Mode controlling whether the order creation simulation can reject orders. /// The operational default lives in `configs::orderbook::OrderSimulationMode` /// (currently `Disabled`). This enum only represents the on-path states @@ -1408,6 +1379,7 @@ mod tests { price_estimation::trade_verifier::code_fetching::MockCodeFetching, serde_json::json, signature_validator::MockSignatureValidating, + simulator::order_simulation::MockOrderSimulating, }; const DEFAULT_ORDER_SIM_TIMEOUT: Duration = Duration::from_secs(2); diff --git a/crates/simulator/src/lib.rs b/crates/simulator/src/lib.rs index 78856d3031..c32edb38e5 100644 --- a/crates/simulator/src/lib.rs +++ b/crates/simulator/src/lib.rs @@ -1,5 +1,6 @@ pub mod encoding; pub mod ethereum; +pub mod order_simulation; pub mod simulation_builder; pub mod tenderly; mod utils; diff --git a/crates/orderbook/src/order_simulation.rs b/crates/simulator/src/order_simulation.rs similarity index 71% rename from crates/orderbook/src/order_simulation.rs rename to crates/simulator/src/order_simulation.rs index 62a87d2be9..a49c6ea159 100644 --- a/crates/orderbook/src/order_simulation.rs +++ b/crates/simulator/src/order_simulation.rs @@ -1,9 +1,5 @@ use { - anyhow::anyhow, - async_trait::async_trait, - model::order::Order, - shared::order_validation::{OrderSimulating, OrderSimulationError}, - simulator::simulation_builder::{ + crate::simulation_builder::{ self, Block, ExecutionAmount, @@ -11,9 +7,37 @@ use { SettlementSimulator, Solver, }, + anyhow::anyhow, + async_trait::async_trait, + model::order::Order, }; -/// Drives `SettlementSimulator` to run a full-order simulation at order +/// Outcome of the order creation simulation. +#[derive(Debug)] +pub enum OrderSimulationError { + /// The simulation ran and the transaction reverted. `reason` is the + /// revert string returned by the EVM (or a Tenderly reason string). + Reverted { + reason: String, + tenderly_url: Option, + }, + /// The simulation could not run (RPC failure, Tenderly error, malformed + /// input, timeout). Treated as fail-open. + Infra(anyhow::Error), +} + +/// Simulates an order's pre-hooks, swap, and post-hooks against the chain. +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] +#[async_trait] +pub trait OrderSimulating: Send + Sync { + async fn simulate( + &self, + order: &Order, + full_app_data: String, + ) -> Result<(), OrderSimulationError>; +} + +/// Drives [`SettlementSimulator`] to run a full-order simulation at order /// creation time, including pre/post hooks, swap, and any wrapper chain. pub struct OrderSimulatorAdapter { inner: SettlementSimulator, From c84adf7d4530e2c19025d11b4509673e84974885 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Wed, 6 May 2026 17:32:22 +0100 Subject: [PATCH 147/154] Drop redundant order_simulation_ prefix from config fields `OrderSimulationConfig.order_simulation_mode` and `OrderSimulationConfig.order_simulation_timeout` repeated the `order_simulation_` prefix that the struct name already conveys. Drop the prefix: - `order_simulation_mode` -> `mode` - `order_simulation_timeout` -> `timeout` - `default_order_simulation_timeout` -> `default_simulation_timeout` TOML keys go from `order-simulation-mode` / `order-simulation-timeout` to `mode` / `timeout`. Updates the parse-test fixtures, the TestDefault impl, run.rs wiring, and the e2e enforce-mode helper. --- crates/configs/src/orderbook/mod.rs | 32 +++++++++---------- .../tests/e2e/eip1271_creation_simulation.rs | 2 +- crates/orderbook/src/run.rs | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index 2d6546b13e..b132808764 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -63,11 +63,11 @@ pub struct OrderSimulationConfig { /// Mode for the order creation simulation. #[serde(default)] - pub order_simulation_mode: OrderSimulationMode, + pub mode: OrderSimulationMode, /// Per-call timeout for the order creation simulation. - #[serde(default = "default_order_simulation_timeout", with = "humantime_serde")] - pub order_simulation_timeout: Duration, + #[serde(default = "default_simulation_timeout", with = "humantime_serde")] + pub timeout: Duration, } /// Mode for the order creation simulation. @@ -80,7 +80,7 @@ pub enum OrderSimulationMode { Disabled, } -fn default_order_simulation_timeout() -> Duration { +fn default_simulation_timeout() -> Duration { Duration::from_secs(2) } @@ -199,8 +199,8 @@ pub mod test_util { Self { gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), tenderly: None, - order_simulation_mode: Default::default(), - order_simulation_timeout: std::time::Duration::from_secs(2), + mode: Default::default(), + timeout: std::time::Duration::from_secs(2), } } } @@ -474,39 +474,39 @@ mod tests { fn parses_simulation_mode_default() { let toml = r#"gas-limit = "16777216""#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Disabled); - assert_eq!(cfg.order_simulation_timeout, Duration::from_secs(2)); + assert_eq!(cfg.mode, OrderSimulationMode::Disabled); + assert_eq!(cfg.timeout, Duration::from_secs(2)); } #[test] fn parses_simulation_mode_enforce() { let toml = r#" gas-limit = "16777216" - order-simulation-mode = "enforce" - order-simulation-timeout = "5s" + mode = "enforce" + timeout = "5s" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Enforce); - assert_eq!(cfg.order_simulation_timeout, Duration::from_secs(5)); + assert_eq!(cfg.mode, OrderSimulationMode::Enforce); + assert_eq!(cfg.timeout, Duration::from_secs(5)); } #[test] fn parses_simulation_mode_shadow() { let toml = r#" gas-limit = "16777216" - order-simulation-mode = "shadow" + mode = "shadow" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Shadow); + assert_eq!(cfg.mode, OrderSimulationMode::Shadow); } #[test] fn parses_simulation_mode_disabled() { let toml = r#" gas-limit = "16777216" - order-simulation-mode = "disabled" + mode = "disabled" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.order_simulation_mode, OrderSimulationMode::Disabled); + assert_eq!(cfg.mode, OrderSimulationMode::Disabled); } } diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index 7d4f280a9f..cb274f9bb1 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -140,7 +140,7 @@ async fn start_services_in_enforce_mode<'a>( ) -> Services<'a> { let orderbook_config = Configuration { order_simulation: Some(OrderSimulationConfig { - order_simulation_mode: OrderSimulationMode::Enforce, + mode: OrderSimulationMode::Enforce, ..TestDefault::test_default() }), ..Configuration::test_default() diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index f4038caea3..996cc972bb 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -424,7 +424,7 @@ pub async fn run(config: Configuration) { .as_ref() .zip(order_simulator.clone()) .and_then(|(sim_config, settlement_simulator)| { - let mode = match sim_config.order_simulation_mode { + let mode = match sim_config.mode { configs::orderbook::OrderSimulationMode::Shadow => OrderSimulationMode::Shadow, configs::orderbook::OrderSimulationMode::Enforce => OrderSimulationMode::Enforce, configs::orderbook::OrderSimulationMode::Disabled => return None, @@ -435,7 +435,7 @@ pub async fn run(config: Configuration) { Some(OrderSimulator { simulator, mode, - timeout: sim_config.order_simulation_timeout, + timeout: sim_config.timeout, }) }); From 953ae861b6de103f8d0c64c64f1df5c72919a552 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 09:38:47 +0100 Subject: [PATCH 148/154] Minor refactor --- crates/shared/src/order_validation.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index b07f0e480e..e512082a97 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -151,17 +151,22 @@ async fn timed_simulation( timeout: Duration, ) -> SimulationOutcome { let _timer = OrderSimulationMetrics::get().simulation_time.start_timer(); - match tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await { - Ok(Ok(())) => SimulationOutcome::Pass, - Ok(Err(OrderSimulationError::Reverted { + let Ok(simulation_result) = + tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await + else { + return SimulationOutcome::Infra(anyhow!("order simulation timeout")); + }; + + match simulation_result { + Ok(()) => SimulationOutcome::Pass, + Err(OrderSimulationError::Reverted { reason, tenderly_url, - })) => SimulationOutcome::Fail { + }) => SimulationOutcome::Fail { reason, tenderly_url, }, - Ok(Err(OrderSimulationError::Infra(err))) => SimulationOutcome::Infra(err), - Err(_) => SimulationOutcome::Infra(anyhow!("order simulation timeout")), + Err(OrderSimulationError::Infra(err)) => SimulationOutcome::Infra(err), } } From edf7993641ac17b8c92c0b85a0a06fbbf5967ecd Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Thu, 7 May 2026 10:04:26 +0000 Subject: [PATCH 149/154] extend instead over overwrite appdata interactions --- crates/simulator/src/simulation_builder.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/simulator/src/simulation_builder.rs b/crates/simulator/src/simulation_builder.rs index 4f5a741fb1..202ef7f79b 100644 --- a/crates/simulator/src/simulation_builder.rs +++ b/crates/simulator/src/simulation_builder.rs @@ -195,8 +195,10 @@ impl SimulationBuilder { pub fn parameters_from_app_data(mut self, app_data: &str) -> Result { let protocol = app_data::parse(app_data.as_bytes()).map_err(BuildError::AppDataParse)?; - self.pre_interactions = self.encode_hooks(&protocol.hooks.pre); - self.post_interactions = self.encode_hooks(&protocol.hooks.post); + self.pre_interactions + .extend(self.encode_hooks(&protocol.hooks.pre)); + self.post_interactions + .extend(self.encode_hooks(&protocol.hooks.post)); match (protocol.wrappers.is_empty(), protocol.flashloan) { (false, Some(_)) => return Err(BuildError::FlashloanWrappersIncompatible), @@ -229,11 +231,11 @@ impl SimulationBuilder { /// Generates 1 interaction executing the given hooks via the trampoline /// contract since executing hooks directly from the settlement contract /// context would give them elevated privileges that put funds at risk. - fn encode_hooks(&self, hooks: &[app_data::Hook]) -> Vec { + fn encode_hooks(&self, hooks: &[app_data::Hook]) -> Option { if hooks.is_empty() { - return vec![]; + return None; } - vec![InteractionData { + Some(InteractionData { target: self.simulator.0.hooks_trampoline, value: U256::ZERO, call_data: contracts::HooksTrampoline::HooksTrampoline::executeCall { @@ -247,7 +249,7 @@ impl SimulationBuilder { .collect(), } .abi_encode(), - }] + }) } /// Instructs the builder to override the settlement contract's buy-token From 2e4d4b781e2e5c5d3c5451ec73c5151b1b88d2d9 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 17:57:28 +0100 Subject: [PATCH 150/154] Add Aave v3 collateral-swap replay negative test Replays a mainnet order that the driver dropped ~295 times on 2026-05-05 with Simulation(Revert("execution reverted")), pinned to block 25_028_258. Asserts the simulator reproduces the on-chain revert at the pre-hook step where the protocol adapter tries to move the trader's aWBTC balance. --- crates/simulator/tests/aave_replay.rs | 110 ++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index e07072c3da..84b75676f4 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -225,3 +225,113 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI .await .expect("failed to build simulation") } + +/// AAVE v3 collateral-swap order +/// `0x441ad034a3c8cd9ad0fc9a9d143c8201bc92d62851a8428997c36a89a03ee2ad4caea9074f2897a3a4ac173c4e5b5bd8b7e3dc976b5f9c6f` +/// dropped 295 times by the mainnet driver on 2026-05-05 (mostly under +/// `arc-solve`) with `Simulation(Revert(_))`. +const NATURALLY_FAILING_APP_DATA: &str = r#"{"appCode":"aave-v3-interface-collateral-swap","metadata":{"flashloan":{"amount":"6136714","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"},"hooks":{"post":[{"callData":"0x398925de00000000000000000000000000000000000000000000000000000000006700b10000000000000000000000000000000000000000000000000000000069850ebf000000000000000000000000000000000000000000000000000000000000001bb32da7ed8403b8369956ffc15f4c1295953004866fa786c0162d28bea3d18b3b08466a876f9a0cb5d1175ef44838426558b19d64b48002231a8c1c90821e08a4","dappId":"cow-sdk://flashloans/aave/v3/collateral-swap","gasLimit":"700000","target":"0x4CAea9074f2897a3A4ac173C4E5b5Bd8b7E3Dc97"}],"pre":[{"callData":"0xb1b6308b000000000000000000000000029d584e847373b6373b01dfad1a0c9bfb9163820000000000000000000000004ec7efb8c873f54c5f62830ec5ecc2362580bdfe0000000000000000000000004caea9074f2897a3a4ac173c4e5b5bd8b7e3dc970000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000005d978d00000000000000000000000000000000000000000000000000000000c9f769e0f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775000000000000000000000000000000000000000000000000000000006b5f9c6f00000000000000000000000000000000000000000000000000000000005da38a0000000000000000000000000000000000000000000000000000000000000bfd00000000000000000000000000000000000000000000000000000000005da38a00000000000000000000000000000000000000000000000000000000c9f769e0","dappId":"cow-sdk://flashloans/aave/v3/collateral-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"limit"},"partnerFee":{"recipient":"0xC542C2F197c4939154017c802B0583C596438380","volumeBps":25},"quote":{"slippageBips":0,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; + +const NATURALLY_FAILING_SIGNATURE_HEX: &str = "0x0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000004caea9074f2897a3a4ac173c4e5b5bd8b7e3dc9700000000000000000000000000000000000000000000000000000000005d978d00000000000000000000000000000000000000000000000000000000c9f769e0000000000000000000000000000000000000000000000000000000006b5f9c6f4223823feadc36c4373c9d88ac1e9875d067e3df5ced70c0a1fedcec396f34d10000000000000000000000000000000000000000000000000000000000000000f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee34677500000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041dd46f4ee5ab3d65846780582c116a93cf37d99be5c87ed1638e3f52bc39861b91595d66c1afd17d585d6ccdbe7b643a2d44b46d1a919f479224e49934c293f231c00000000000000000000000000000000000000000000000000000000000000"; + +/// Replay of a real production order that the mainnet driver was repeatedly +/// dropping with `Simulation(Revert(_))`. The pre-hook reverts because the +/// trader's aToken balance is insufficient for the collateral swap. +#[tokio::test] +#[ignore] +async fn aave_collateral_swap_replay_fails_naturally() { + let Ok(rpc_url) = std::env::var("MAINNET_RPC_URL") else { + eprintln!("MAINNET_RPC_URL not set - skipping replay test"); + return; + }; + + let canonical_app_data = canonicalise_app_data(NATURALLY_FAILING_APP_DATA); + let inputs = build_naturally_failing_replay_simulation(&rpc_url, &canonical_app_data).await; + + let err = inputs + .simulate() + .await + .expect_err("simulation must revert: pre-hook moves aToken the trader no longer holds"); + let msg = format!("{err:?}"); + assert!( + msg.contains("execution reverted"), + "expected an EVM revert, got: {msg}", + ); +} + +async fn build_naturally_failing_replay_simulation( + rpc_url: &str, + full_app_data: &str, +) -> EthCallInputs { + // Block taken from a `BlockNo(...)` embedded in one of the dropped- + // solution events for this order on 2026-05-05. + let fork_block_mainnet = 25_028_258u64; + let chain_id = 1u64; + let order_owner = address!("4caea9074f2897a3a4ac173c4e5b5bd8b7e3dc97"); + let sell_token_a_wbtc = address!("2260fac5e5542a773aa44fbcfedf7c193bc2c599"); + let buy_token_usdt = address!("dac17f958d2ee523a2206206994597c13d831ec7"); + let sell_amount = U256::from_str("6133645").unwrap(); + let buy_amount = U256::from_str("3388434912").unwrap(); + let valid_to = 1_801_428_079u32; // 2027-01-31 ish + + let web3 = ethrpc::Web3::new_from_url(rpc_url); + let provider = web3.provider.clone(); + + let settlement = contracts::GPv2Settlement::Instance::deployed(&provider) + .await + .expect("settlement contract not deployed on mainnet?"); + + let flash_loan_router = contracts::FlashLoanRouter::deployment_address(&chain_id) + .expect("FlashLoanRouter deployment address"); + let hooks_trampoline = contracts::HooksTrampoline::deployment_address(&chain_id) + .expect("HooksTrampoline deployment address"); + + let balance_overrider = Arc::new(balance_overrides::BalanceOverrides::new(web3)); + let block_stream = ethrpc::block_stream::mock_single_block(Default::default()); + + let simulator = SettlementSimulator::new( + settlement, + flash_loan_router, + hooks_trampoline, + sell_token_a_wbtc, + 30_000_000u64, + balance_overrider, + block_stream, + None, + ) + .await + .expect("failed to create SettlementSimulator"); + + let signature_bytes = hex::decode(NATURALLY_FAILING_SIGNATURE_HEX.trim_start_matches("0x")) + .expect("NATURALLY_FAILING_SIGNATURE_HEX must be valid hex"); + let app_data_hash = AppDataHash(hash_full_app_data(full_app_data.as_bytes())); + + let order_data = OrderData { + sell_token: sell_token_a_wbtc, + buy_token: buy_token_usdt, + receiver: Some(order_owner), + sell_amount, + buy_amount, + valid_to, + app_data: app_data_hash, + fee_amount: U256::ZERO, + kind: OrderKind::Sell, + partially_fillable: false, + sell_token_balance: SellTokenSource::Erc20, + buy_token_balance: BuyTokenDestination::Erc20, + }; + + simulator + .new_simulation_builder() + .with_orders([simulation_builder::Order::new(order_data) + .with_signature(order_owner, Signature::Eip1271(signature_bytes)) + .fill_at(ExecutionAmount::Full, PriceEncoding::LimitPrice)]) + .parameters_from_app_data(full_app_data) + .expect("parameters_from_app_data should parse the app data") + .from_solver(Solver::Fake(None)) + .provide_sufficient_buy_tokens() + .at_block(Block::Number(fork_block_mainnet)) + .build() + .await + .expect("failed to build simulation") +} From 76eab541f7c5fb720ed54d4d2e216c6a96bcac87 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 18:17:32 +0100 Subject: [PATCH 151/154] Trim verbose comments in aave_replay tests --- crates/simulator/tests/aave_replay.rs | 46 +++++++-------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 84b75676f4..48b62536ba 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -58,42 +58,23 @@ const APP_DATA: &str = r#"{ "version": "1.14.0" }"#; -/// Returns `app_data` minified with object keys sorted alphabetically. -/// -/// The sort comes from `serde_json::Value::Object`'s `BTreeMap` backing, which -/// applies whenever the `preserve_order` feature is not enabled (it isn't in -/// this workspace). The signed production payload happens to already be -/// alphabetically keyed at every level, so the output matches it byte for -/// byte. +/// Minifies `app_data` with keys sorted alphabetically. fn canonicalise_app_data(app_data: &str) -> String { let value: serde_json::Value = serde_json::from_str(app_data).expect("APP_DATA must be valid JSON"); serde_json::to_string(&value).expect("re-serialising must succeed") } -/// Production EIP-1271 signature blob for the replayed order. The trader's -/// signer contract decodes it and validates against the order hash. +/// Production EIP-1271 signature blob for the replayed order. const SIGNATURE_HEX: &str = "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000e58acb86761699c1cbc665e6b7e0271503f6336c0000000000000000000000000000000000000000000000003e14904047a25ee600000000000000000000000000000000000000000000021e4382edd5a86c00000000000000000000000000000000000000000000000000000000000069f323f8a1435054976e030f531f620f051bbabe34ef387901808b8677cf7c9304c21f3c00000000000000000000000000000000000000000000000000000000000000006ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc00000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041bb5488854dd5149f8843514851b7e25499917ca742af77061d2355681f3b608157bb34a59f9632e2228ea869c6d571f822295ee2eb03904dd8dd874245478f3b1b00000000000000000000000000000000000000000000000000000000000000"; -/// Replay of a real Aave v3 debt-swap order against a historical mainnet -/// block, exercising the prototype's `SettlementSimulator`-based simulation -/// path end-to-end without involving the orderbook's wall-clock validity -/// check. -/// -/// Why this test exists: -/// -/// - A full `OrderValidator::validate_and_construct_order` flow uses -/// `SystemTime::now()` to bound `valid_to`, which makes any historical order -/// replay non-deterministic (the test rots as the order expires). -/// - The prototype's value lives in the simulation, not the validity check, so -/// we exercise the simulation directly: build a `SettlementSimulator` against -/// a real RPC, pin the simulation to the block right before settlement, and -/// assert it does not revert. -/// -/// Order replayed: an `aave-v3-interface-debt-swap` order +/// AAVE v3 debt-swap order /// `0x7f5df255b55f5eba3034f74acb8e91a04aaf61a755b88c61ad7c61068856f3b2e58acb86761699c1cbc665e6b7e0271503f6336c69f323f8`, -/// sell WETH, buy GHO. The owner is an EIP-1167 minimal proxy that the -/// pre-hook deploys just-in-time. +/// owner is an EIP-1167 minimal proxy the pre-hook deploys JIT. +/// +/// We exercise `SettlementSimulator` directly instead of going through +/// `OrderValidator::validate_and_construct_order`, which bounds `valid_to` +/// by `SystemTime::now()` and would make this test rot. #[tokio::test] #[ignore] async fn aave_debt_swap_replay() { @@ -147,14 +128,11 @@ async fn aave_debt_swap_replay_fails_when_flashloan_oversubscribed() { ); } -/// Builds a simulation pinned to the block right before the Aave debt-swap -/// settlement. The caller controls `full_app_data` so the same wiring -/// supports a positive replay (untouched) and a negative replay (tampered). +/// Shared builder for the positive replay (`APP_DATA`) and the +/// flashloan-tampered negative replay. async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallInputs { - // One block before the on-chain settlement transaction. At this block - // the helper-clone owner contract has no code yet (the pre-hook deploys - // it), the protocol-adapter factory is live, and Aave v3 has WETH - // liquidity. + // Pinned one block before the on-chain settlement: pre-hook hasn't + // yet deployed the owner contract, Aave has WETH liquidity. let fork_block_mainnet = 24_992_051u64; let order_owner = address!("e58aCB86761699c1cBC665e6b7E0271503f6336C"); let sell_token_weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); From b113f0009fc77fec91a49963cb28f56cd83889bb Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 18:20:18 +0100 Subject: [PATCH 152/154] Trim the comment --- crates/simulator/tests/aave_replay.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/simulator/tests/aave_replay.rs b/crates/simulator/tests/aave_replay.rs index 48b62536ba..a5308d980f 100644 --- a/crates/simulator/tests/aave_replay.rs +++ b/crates/simulator/tests/aave_replay.rs @@ -206,8 +206,7 @@ async fn build_replay_simulation(rpc_url: &str, full_app_data: &str) -> EthCallI /// AAVE v3 collateral-swap order /// `0x441ad034a3c8cd9ad0fc9a9d143c8201bc92d62851a8428997c36a89a03ee2ad4caea9074f2897a3a4ac173c4e5b5bd8b7e3dc976b5f9c6f` -/// dropped 295 times by the mainnet driver on 2026-05-05 (mostly under -/// `arc-solve`) with `Simulation(Revert(_))`. +/// which reverts onchain due to corrupted hooks const NATURALLY_FAILING_APP_DATA: &str = r#"{"appCode":"aave-v3-interface-collateral-swap","metadata":{"flashloan":{"amount":"6136714","liquidityProvider":"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2","protocolAdapter":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","receiver":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192","token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"},"hooks":{"post":[{"callData":"0x398925de00000000000000000000000000000000000000000000000000000000006700b10000000000000000000000000000000000000000000000000000000069850ebf000000000000000000000000000000000000000000000000000000000000001bb32da7ed8403b8369956ffc15f4c1295953004866fa786c0162d28bea3d18b3b08466a876f9a0cb5d1175ef44838426558b19d64b48002231a8c1c90821e08a4","dappId":"cow-sdk://flashloans/aave/v3/collateral-swap","gasLimit":"700000","target":"0x4CAea9074f2897a3A4ac173C4E5b5Bd8b7E3Dc97"}],"pre":[{"callData":"0xb1b6308b000000000000000000000000029d584e847373b6373b01dfad1a0c9bfb9163820000000000000000000000004ec7efb8c873f54c5f62830ec5ecc2362580bdfe0000000000000000000000004caea9074f2897a3a4ac173c4e5b5bd8b7e3dc970000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000005d978d00000000000000000000000000000000000000000000000000000000c9f769e0f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775000000000000000000000000000000000000000000000000000000006b5f9c6f00000000000000000000000000000000000000000000000000000000005da38a0000000000000000000000000000000000000000000000000000000000000bfd00000000000000000000000000000000000000000000000000000000005da38a00000000000000000000000000000000000000000000000000000000c9f769e0","dappId":"cow-sdk://flashloans/aave/v3/collateral-swap","gasLimit":"300000","target":"0xdeCC46a4b09162F5369c5C80383AAa9159bCf192"}]},"orderClass":{"orderClass":"limit"},"partnerFee":{"recipient":"0xC542C2F197c4939154017c802B0583C596438380","volumeBps":25},"quote":{"slippageBips":0,"smartSlippage":true},"utm":{"utmCampaign":"developer-cohort","utmContent":"","utmMedium":"cow-sdk@7.3.4","utmSource":"cowmunity","utmTerm":"js"}},"version":"1.14.0"}"#; const NATURALLY_FAILING_SIGNATURE_HEX: &str = "0x0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000004caea9074f2897a3a4ac173c4e5b5bd8b7e3dc9700000000000000000000000000000000000000000000000000000000005d978d00000000000000000000000000000000000000000000000000000000c9f769e0000000000000000000000000000000000000000000000000000000006b5f9c6f4223823feadc36c4373c9d88ac1e9875d067e3df5ced70c0a1fedcec396f34d10000000000000000000000000000000000000000000000000000000000000000f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee34677500000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041dd46f4ee5ab3d65846780582c116a93cf37d99be5c87ed1638e3f52bc39861b91595d66c1afd17d585d6ccdbe7b643a2d44b46d1a919f479224e49934c293f231c00000000000000000000000000000000000000000000000000000000000000"; From 18be75fbbd16077a1f2fae293cc569bab8ff8f3a Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 19:02:01 +0100 Subject: [PATCH 153/154] Simplify EIP-1271 order simulation to shadow mode Drops the disabled / shadow / enforce mode enum and the per-outcome metric matrix. Order simulation now runs alongside the signature check purely for observability. Disagreements are logged. The signature check alone decides whether the order is accepted. Enforce mode (rejecting orders whose simulation reverts) will land in a follow-up PR. The negative e2e test that asserted HTTP 400 OrderSimulationFailed comes back with it. --- crates/configs/src/orderbook/mod.rs | 43 +-- .../tests/e2e/eip1271_creation_simulation.rs | 126 +------- crates/orderbook/src/api/post_order.rs | 5 - crates/orderbook/src/run.rs | 19 +- crates/shared/src/order_validation.rs | 293 +++++------------- 5 files changed, 93 insertions(+), 393 deletions(-) diff --git a/crates/configs/src/orderbook/mod.rs b/crates/configs/src/orderbook/mod.rs index b132808764..84205ed112 100644 --- a/crates/configs/src/orderbook/mod.rs +++ b/crates/configs/src/orderbook/mod.rs @@ -61,25 +61,11 @@ pub struct OrderSimulationConfig { #[serde(default)] pub tenderly: Option, - /// Mode for the order creation simulation. - #[serde(default)] - pub mode: OrderSimulationMode, - /// Per-call timeout for the order creation simulation. #[serde(default = "default_simulation_timeout", with = "humantime_serde")] pub timeout: Duration, } -/// Mode for the order creation simulation. -#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub enum OrderSimulationMode { - Shadow, - Enforce, - #[default] - Disabled, -} - fn default_simulation_timeout() -> Duration { Duration::from_secs(2) } @@ -199,7 +185,6 @@ pub mod test_util { Self { gas_limit: U256::try_from(16777215).expect("u64 can be converted to U256"), tenderly: None, - mode: Default::default(), timeout: std::time::Duration::from_secs(2), } } @@ -471,42 +456,20 @@ mod tests { } #[test] - fn parses_simulation_mode_default() { + fn parses_order_simulation_defaults() { let toml = r#"gas-limit = "16777216""#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.mode, OrderSimulationMode::Disabled); assert_eq!(cfg.timeout, Duration::from_secs(2)); + assert!(cfg.tenderly.is_none()); } #[test] - fn parses_simulation_mode_enforce() { + fn parses_order_simulation_full() { let toml = r#" gas-limit = "16777216" - mode = "enforce" timeout = "5s" "#; let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.mode, OrderSimulationMode::Enforce); assert_eq!(cfg.timeout, Duration::from_secs(5)); } - - #[test] - fn parses_simulation_mode_shadow() { - let toml = r#" - gas-limit = "16777216" - mode = "shadow" - "#; - let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.mode, OrderSimulationMode::Shadow); - } - - #[test] - fn parses_simulation_mode_disabled() { - let toml = r#" - gas-limit = "16777216" - mode = "disabled" - "#; - let cfg: OrderSimulationConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.mode, OrderSimulationMode::Disabled); - } } diff --git a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs index cb274f9bb1..7feff4f72c 100644 --- a/crates/e2e/tests/e2e/eip1271_creation_simulation.rs +++ b/crates/e2e/tests/e2e/eip1271_creation_simulation.rs @@ -1,86 +1,28 @@ -//! Local-node tests for the EIP-1271 creation-time simulation. +//! Local-node smoke test for the EIP-1271 creation-time simulation wiring. //! -//! - Negative: a Safe-signed order whose `app_data.protocol.wrappers` points at -//! a custom always-revert wrapper. With the orderbook in -//! `OrderSimulationMode::Enforce`, the simulation drives `settle()` through -//! the wrapper, the wrapper reverts, and the API rejects with HTTP 400 -//! `OrderSimulationFailed`. A wrapper is used rather than a buggy pre-hook -//! because `HooksTrampoline.execute` deliberately swallows hook reverts, so a -//! buggy hook would not surface as a simulation failure. -//! - Positive: a Safe-signed order with empty app_data is accepted, proving the -//! adapter wiring lets healthy orders through. +//! A Safe-signed order with empty `app_data` is accepted, proving that +//! `OrderSimulator` runs alongside the cheap signature check without +//! disrupting the happy path. The simulation runs in shadow mode (logs +//! disagreements, never rejects). An enforce-mode rejection test will be +//! added together with the enforce-mode follow-up PR. use { - alloy::{ - primitives::{Address, Bytes, TxKind, hex}, - providers::Provider, - rpc::types::TransactionRequest, - }, configs::{ - orderbook::{Configuration, OrderSimulationConfig, OrderSimulationMode}, + orderbook::{Configuration, OrderSimulationConfig}, test_util::TestDefault, }, e2e::setup::{MintableToken, OnchainComponents, Services, run_test, safe::Safe}, model::order::{OrderCreation, OrderCreationAppData, OrderKind}, number::units::EthUnit, - reqwest::StatusCode, - serde_json::json, shared::web3::Web3, }; -/// Constructor + runtime that always reverts with empty data on any call. -/// -/// Constructor copies the 5-byte runtime (`PUSH1 0; PUSH1 0; REVERT`) into -/// memory and returns it. The deployed contract reverts unconditionally -/// regardless of selector or calldata. -const ALWAYS_REVERT_INIT_CODE: [u8; 17] = hex!("6005600c60003960056000f360006000fd"); - -#[tokio::test] -#[ignore] -async fn local_node_eip1271_creation_simulation_rejects_when_wrapper_reverts() { - run_test(rejects_when_wrapper_reverts).await; -} - #[tokio::test] #[ignore] async fn local_node_eip1271_creation_simulation_accepts_valid_order() { run_test(accepts_valid_order).await; } -async fn rejects_when_wrapper_reverts(web3: Web3) { - let mut onchain = OnchainComponents::deploy(web3.clone()).await; - let [solver] = onchain.make_solvers(1u64.eth()).await; - let [trader] = onchain.make_accounts(1u64.eth()).await; - - let safe = Safe::deploy(trader.clone(), web3.provider.clone()).await; - - let [token] = onchain - .deploy_tokens_with_weth_uni_v2_pools(100_000u64.eth(), 100_000u64.eth()) - .await; - fund_safe(&safe, &token, &onchain).await; - - let services = start_services_in_enforce_mode(&onchain, solver).await; - - let wrapper_addr = deploy_always_revert(&web3, trader.address()).await; - - let order = sign_order( - &safe, - &onchain, - &token, - Some(WrapperRef { - address: wrapper_addr, - data: vec![], - }), - ); - - let (status, body) = services.create_order(&order).await.unwrap_err(); - assert_eq!(status, StatusCode::BAD_REQUEST, "body: {body}"); - assert!( - body.contains("OrderSimulationFailed"), - "expected OrderSimulationFailed in body, got: {body}", - ); -} - async fn accepts_valid_order(web3: Web3) { let mut onchain = OnchainComponents::deploy(web3.clone()).await; let [solver] = onchain.make_solvers(1u64.eth()).await; @@ -93,9 +35,9 @@ async fn accepts_valid_order(web3: Web3) { .await; fund_safe(&safe, &token, &onchain).await; - let services = start_services_in_enforce_mode(&onchain, solver).await; + let services = start_services_with_simulation(&onchain, solver).await; - let order = sign_order(&safe, &onchain, &token, None); + let order = sign_order(&safe, &onchain, &token); let uid = services .create_order(&order) @@ -105,25 +47,6 @@ async fn accepts_valid_order(web3: Web3) { assert_eq!(stored.metadata.uid, uid); } -/// Deploys a contract whose runtime is `PUSH1 0; PUSH1 0; REVERT`. -async fn deploy_always_revert(web3: &Web3, from: Address) -> Address { - let mut tx = TransactionRequest::default() - .from(from) - .input(Bytes::from(ALWAYS_REVERT_INIT_CODE.to_vec()).into()); - tx.to = Some(TxKind::Create); - let receipt = web3 - .provider - .send_transaction(tx) - .await - .unwrap() - .get_receipt() - .await - .unwrap(); - receipt - .contract_address - .expect("deployment receipt should carry the contract address") -} - async fn fund_safe(safe: &Safe, token: &MintableToken, onchain: &OnchainComponents) { token.mint(safe.address(), 10u64.eth()).await; safe.exec_alloy_call( @@ -134,15 +57,12 @@ async fn fund_safe(safe: &Safe, token: &MintableToken, onchain: &OnchainComponen .await; } -async fn start_services_in_enforce_mode<'a>( +async fn start_services_with_simulation<'a>( onchain: &'a OnchainComponents, solver: e2e::setup::onchain_components::TestAccount, ) -> Services<'a> { let orderbook_config = Configuration { - order_simulation: Some(OrderSimulationConfig { - mode: OrderSimulationMode::Enforce, - ..TestDefault::test_default() - }), + order_simulation: Some(OrderSimulationConfig::test_default()), ..Configuration::test_default() }; @@ -157,31 +77,11 @@ async fn start_services_in_enforce_mode<'a>( services } -struct WrapperRef { - address: Address, - data: Vec, -} - fn sign_order( safe: &Safe, onchain: &OnchainComponents, sell_token: &MintableToken, - wrapper: Option, ) -> OrderCreation { - let app_data = match wrapper { - Some(w) => json!({ - "metadata": { - "wrappers": [{ - "address": format!("{:?}", w.address), - "data": format!("0x{}", hex::encode(&w.data)), - "isOmittable": false, - }], - }, - }), - None => json!({}), - } - .to_string(); - let mut order = OrderCreation { kind: OrderKind::Sell, sell_token: *sell_token.address(), @@ -190,7 +90,9 @@ fn sign_order( buy_amount: 1u64.eth(), valid_to: model::time::now_in_epoch_seconds() + 300, from: Some(safe.address()), - app_data: OrderCreationAppData::Full { full: app_data }, + app_data: OrderCreationAppData::Full { + full: "{}".to_string(), + }, ..Default::default() }; safe.sign_order(&mut order, onchain); diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index 3c63507428..9db253913b 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -188,11 +188,6 @@ impl IntoResponse for ValidationErrorWrapper { ), ) .into_response(), - ValidationError::SimulationFailed(reason) => ( - StatusCode::BAD_REQUEST, - error("OrderSimulationFailed", reason), - ) - .into_response(), ValidationError::InsufficientBalance => ( StatusCode::BAD_REQUEST, error( diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 996cc972bb..bc18f1c064 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -41,12 +41,7 @@ use { }, shared::{ order_quoting::{self, OrderQuoter}, - order_validation::{ - OrderSimulationMode, - OrderSimulator, - OrderValidPeriodConfiguration, - OrderValidator, - }, + order_validation::{OrderSimulator, OrderValidPeriodConfiguration, OrderValidator}, }, std::{future::Future, net::SocketAddr, sync::Arc, time::Duration}, token_info::{CachedTokenInfoFetcher, TokenInfoFetcher}, @@ -423,20 +418,14 @@ pub async fn run(config: Configuration) { .order_simulation .as_ref() .zip(order_simulator.clone()) - .and_then(|(sim_config, settlement_simulator)| { - let mode = match sim_config.mode { - configs::orderbook::OrderSimulationMode::Shadow => OrderSimulationMode::Shadow, - configs::orderbook::OrderSimulationMode::Enforce => OrderSimulationMode::Enforce, - configs::orderbook::OrderSimulationMode::Disabled => return None, - }; + .map(|(sim_config, settlement_simulator)| { let simulator: Arc = Arc::new( simulator::order_simulation::OrderSimulatorAdapter::new(settlement_simulator), ); - Some(OrderSimulator { + OrderSimulator { simulator, - mode, timeout: sim_config.timeout, - }) + } }); let order_validator = Arc::new(OrderValidator::new( diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index e512082a97..4b6f007f88 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -49,108 +49,34 @@ use { tracing::instrument, }; -/// Mode controlling whether the order creation simulation can reject orders. -/// The operational default lives in `configs::orderbook::OrderSimulationMode` -/// (currently `Disabled`). This enum only represents the on-path states -/// `OrderValidator` actually executes. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum OrderSimulationMode { - /// Log disagreements, emit metrics. Never reject. - Shadow, - /// If the signature check passes but the order simulation fails, reject - /// the order with `ValidationError::SimulationFailed`. Infra errors - /// still never reject (fail-open). - Enforce, -} - -/// Runs a full order simulation alongside the cheap EIP-1271 signature -/// check and decides, based on the configured mode, whether a failure -/// should reject the order. +/// Runs the full order simulation alongside the cheap EIP-1271 signature +/// check. Disagreements are logged. The simulation never rejects the order +/// (shadow-mode behaviour). An enforce-mode follow-up will use the same +/// simulator output to reject orders whose simulation reverts. #[derive(Clone)] pub struct OrderSimulator { pub simulator: Arc, - pub mode: OrderSimulationMode, pub timeout: Duration, } -#[derive(prometheus_metric_storage::MetricStorage, Clone, Debug)] -#[metric(subsystem = "order_simulation")] -struct OrderSimulationMetrics { - /// Counts each (signature-check, simulation) outcome pair. The signature - /// axis takes `pass | fail | infra | skipped`; the simulation axis - /// takes `pass | fail | infra`. - #[metric(labels("signature", "simulation"))] - total: prometheus::IntCounterVec, - /// Time spent in the order creation simulation. - simulation_time: prometheus::Histogram, -} - -impl OrderSimulationMetrics { - fn get() -> &'static Self { - Self::instance(observe::metrics::get_storage_registry()) - .expect("unexpected error getting OrderSimulationMetrics instance") - } -} - -#[derive(Copy, Clone, Debug)] -enum SignatureOutcome { - Pass, - Fail, - Infra, - /// `eip1271_skip_creation_validation` is set — the signature check was - /// not run, only the simulation was. - Skipped, -} - #[derive(Debug)] enum SimulationOutcome { Pass, - Fail { + Reverted { reason: String, tenderly_url: Option, }, Infra(anyhow::Error), } -impl SignatureOutcome { - fn label(&self) -> &'static str { - match self { - Self::Pass => "pass", - Self::Fail => "fail", - Self::Infra => "infra", - Self::Skipped => "skipped", - } - } -} - -impl SimulationOutcome { - fn label(&self) -> &'static str { - match self { - Self::Pass => "pass", - Self::Fail { .. } => "fail", - Self::Infra(_) => "infra", - } - } -} - -fn classify_signature(res: &Result) -> SignatureOutcome { - match res { - Ok(_) => SignatureOutcome::Pass, - Err(SignatureValidationError::Invalid) => SignatureOutcome::Fail, - Err(SignatureValidationError::Other(_)) => SignatureOutcome::Infra, - } -} - -/// Runs a simulation under the given timeout, recording the duration in the -/// `simulation_time` histogram, and folds the timeout / revert / infra error -/// cases into a single `SimulationOutcome`. +/// Runs a simulation under the given timeout and folds the +/// timeout / revert / infra cases into a single [`SimulationOutcome`]. async fn timed_simulation( sim: &dyn OrderSimulating, order: &Order, full_app_data: String, timeout: Duration, ) -> SimulationOutcome { - let _timer = OrderSimulationMetrics::get().simulation_time.start_timer(); let Ok(simulation_result) = tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await else { @@ -162,7 +88,7 @@ async fn timed_simulation( Err(OrderSimulationError::Reverted { reason, tenderly_url, - }) => SimulationOutcome::Fail { + }) => SimulationOutcome::Reverted { reason, tenderly_url, }, @@ -170,36 +96,30 @@ async fn timed_simulation( } } -fn record_simulation_outcome( - signature: SignatureOutcome, +/// Logs the simulation result alongside the signature-check outcome. +/// Cases that disagree (sig pass + sim revert, sig fail + sim pass) are +/// surfaced as warnings, infra errors are logged separately. +fn log_simulation_outcome( + signature: &Result, simulation: &SimulationOutcome, order_uid: OrderUid, ) { - OrderSimulationMetrics::get() - .total - .with_label_values(&[signature.label(), simulation.label()]) - .inc(); - match (signature, simulation) { ( - SignatureOutcome::Pass, - SimulationOutcome::Fail { + Ok(_), + SimulationOutcome::Reverted { reason, tenderly_url, }, ) => tracing::warn!( ?order_uid, - signature = signature.label(), - simulation = simulation.label(), ?reason, ?tenderly_url, - "order simulation disagreement", + "order simulation disagreement: signature passed, simulation reverted", ), - (SignatureOutcome::Fail, SimulationOutcome::Pass) => tracing::warn!( + (Err(SignatureValidationError::Invalid), SimulationOutcome::Pass) => tracing::warn!( ?order_uid, - signature = signature.label(), - simulation = simulation.label(), - "order simulation disagreement", + "order simulation disagreement: signature invalid, simulation passed", ), (_, SimulationOutcome::Infra(err)) => { tracing::warn!(?order_uid, ?err, "order simulation infra error") @@ -220,19 +140,15 @@ async fn run_order_simulation_only( config.timeout, ) .await; - OrderSimulationMetrics::get() - .total - .with_label_values(&[SignatureOutcome::Skipped.label(), outcome.label()]) - .inc(); match &outcome { - SimulationOutcome::Fail { + SimulationOutcome::Reverted { reason, tenderly_url, } => tracing::warn!( order_uid = %preview_order.metadata.uid, ?reason, ?tenderly_url, - "order simulation (signature check skipped)", + "order simulation reverted (signature check skipped)", ), SimulationOutcome::Infra(err) => tracing::warn!( order_uid = %preview_order.metadata.uid, @@ -348,10 +264,6 @@ pub enum ValidationError { /// An invalid EIP-1271 signature, where the on-chain validation check /// reverted or did not return the expected value. InvalidEip1271Signature(B256), - /// The EIP-1271 order simulation returned a revert in enforce mode. Only - /// possible when the 1271 signature check passed but the full - /// order simulation failed. - SimulationFailed(String), ZeroAmount, IncompatibleSigningScheme, TooManyLimitOrders, @@ -621,16 +533,10 @@ impl OrderValidator { } /// Runs the cheap `isValidSignature` check and, when a simulator is - /// configured, the full order simulation concurrently. Decides the - /// outcome: - /// - /// - signature `Invalid` → `InvalidEip1271Signature` (current behaviour). - /// - signature `Other` → `ValidationError::Other` (infra error). - /// - signature `Ok(gas)` + simulation `Reverted` + **Enforce** mode → - /// `SimulationFailed(reason)` (the new rejection added by this PR). - /// - signature `Ok(gas)` in every other combination → `Ok(gas)`. - /// - /// Simulation infra errors (RPC / Tenderly / timeout) never reject. + /// configured, the full order simulation concurrently. The signature + /// result decides whether the order is accepted. The simulation result + /// is logged for observability (shadow mode), it never rejects. + /// Simulation infra errors (RPC / Tenderly / timeout) are logged. async fn run_eip1271_with_signature_check( &self, check: SignatureCheck, @@ -649,6 +555,10 @@ impl OrderValidator { }); }; + // Shadow mode: the simulation runs for observability only. Disagreements + // are logged below and never affect the return value. The enforce-mode + // follow-up will consume `simulation_outcome` here to reject orders + // whose simulation reverts. let simulation_fut = timed_simulation( config.simulator.as_ref(), preview_order, @@ -658,23 +568,16 @@ impl OrderValidator { let (signature_res, simulation_outcome) = tokio::join!(signature_fut, simulation_fut); - let signature_outcome = classify_signature(&signature_res); - record_simulation_outcome( - signature_outcome, + log_simulation_outcome( + &signature_res, &simulation_outcome, preview_order.metadata.uid, ); - match (signature_res, &simulation_outcome, config.mode) { - (Ok(_gas), SimulationOutcome::Fail { reason, .. }, OrderSimulationMode::Enforce) => { - Err(ValidationError::SimulationFailed(reason.clone())) - } - (Ok(gas), _, _) => Ok(gas), - (Err(SignatureValidationError::Invalid), _, _) => { - Err(ValidationError::InvalidEip1271Signature(hash)) - } - (Err(SignatureValidationError::Other(err)), _, _) => Err(ValidationError::Other(err)), - } + signature_res.map_err(|err| match err { + SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash), + SignatureValidationError::Other(err) => ValidationError::Other(err), + }) } async fn check_max_limit_orders(&self, owner: Address) -> Result<(), ValidationError> { @@ -2987,10 +2890,9 @@ mod tests { } } - fn simulator_with_mode(sim: MockOrderSimulating, mode: OrderSimulationMode) -> OrderSimulator { + fn order_simulator(sim: MockOrderSimulating) -> OrderSimulator { OrderSimulator { simulator: Arc::new(sim), - mode, timeout: DEFAULT_ORDER_SIM_TIMEOUT, } } @@ -3040,15 +2942,11 @@ mod tests { ) } - /// Verifies the full (signature × simulation × mode) outcome matrix. - /// - /// Only the `(signature Pass, simulation Fail, Enforce)` cell changes - /// behaviour relative to today. Every other cell must match the - /// signature-only behaviour from before order simulation was added. + /// Verifies the (signature × simulation) outcome matrix in shadow mode. + /// The signature result alone decides acceptance; the simulation result + /// is observed only. #[tokio::test] async fn signature_and_simulation_outcome_matrix() { - use OrderSimulationMode::{Enforce, Shadow}; - #[derive(Copy, Clone, Debug)] enum Sig { Pass, @@ -3063,37 +2961,17 @@ mod tests { enum Expected { Accepted, InvalidSignature, - SimulationFailed, } - let cases: &[(Sig, Sim, OrderSimulationMode, Expected)] = &[ - (Sig::Pass, Sim::Pass, Shadow, Expected::Accepted), - (Sig::Pass, Sim::Reverted, Shadow, Expected::Accepted), - (Sig::Invalid, Sim::Pass, Shadow, Expected::InvalidSignature), - ( - Sig::Invalid, - Sim::Reverted, - Shadow, - Expected::InvalidSignature, - ), - (Sig::Pass, Sim::Pass, Enforce, Expected::Accepted), - ( - Sig::Pass, - Sim::Reverted, - Enforce, - Expected::SimulationFailed, - ), - (Sig::Invalid, Sim::Pass, Enforce, Expected::InvalidSignature), - ( - Sig::Invalid, - Sim::Reverted, - Enforce, - Expected::InvalidSignature, - ), + let cases: &[(Sig, Sim, Expected)] = &[ + (Sig::Pass, Sim::Pass, Expected::Accepted), + (Sig::Pass, Sim::Reverted, Expected::Accepted), + (Sig::Invalid, Sim::Pass, Expected::InvalidSignature), + (Sig::Invalid, Sim::Reverted, Expected::InvalidSignature), ]; - for &(sig, simulation, mode, expected) in cases { - let label = format!("sig={sig:?} sim={simulation:?} mode={mode:?}"); + for &(sig, simulation, expected) in cases { + let label = format!("sig={sig:?} sim={simulation:?}"); let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() @@ -3110,11 +2988,8 @@ mod tests { tenderly_url: None, }), }); - let validator = build_1271_validator( - signature_validator, - Some(simulator_with_mode(sim, mode)), - false, - ); + let validator = + build_1271_validator(signature_validator, Some(order_simulator(sim)), false); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3129,46 +3004,30 @@ mod tests { matches!(result, Err(ValidationError::InvalidEip1271Signature(_))), "{label}: got {result:?}" ), - Expected::SimulationFailed => assert!( - matches!(result, Err(ValidationError::SimulationFailed(_))), - "{label}: got {result:?}" - ), } } } #[tokio::test] - async fn simulation_infra_error_is_fail_open_in_both_modes() { - for mode in [OrderSimulationMode::Shadow, OrderSimulationMode::Enforce] { - let mut signature_validator = MockSignatureValidating::new(); - signature_validator - .expect_validate_signature_and_get_additional_gas() - .returning(|_| Ok(0u64)); - let mut sim = MockOrderSimulating::new(); - sim.expect_simulate() - .returning(|_, _| Err(OrderSimulationError::Infra(anyhow!("RPC down")))); - let validator = build_1271_validator( - signature_validator, - Some(OrderSimulator { - simulator: Arc::new(sim), - mode, - timeout: DEFAULT_ORDER_SIM_TIMEOUT, - }), - false, - ); - let result = validator - .validate_and_construct_order( - make_1271_order_creation(), - &DomainSeparator::default(), - Default::default(), - None, - ) - .await; - assert!( - result.is_ok(), - "expected Ok for mode={mode:?}, got {result:?}" - ); - } + async fn simulation_infra_error_is_fail_open() { + let mut signature_validator = MockSignatureValidating::new(); + signature_validator + .expect_validate_signature_and_get_additional_gas() + .returning(|_| Ok(0u64)); + let mut sim = MockOrderSimulating::new(); + sim.expect_simulate() + .returning(|_, _| Err(OrderSimulationError::Infra(anyhow!("RPC down")))); + let validator = + build_1271_validator(signature_validator, Some(order_simulator(sim)), false); + let result = validator + .validate_and_construct_order( + make_1271_order_creation(), + &DomainSeparator::default(), + Default::default(), + None, + ) + .await; + assert!(result.is_ok(), "expected Ok, got {result:?}"); } #[tokio::test] @@ -3186,11 +3045,7 @@ mod tests { tenderly_url: None, }) }); - let validator = build_1271_validator( - signature_validator, - Some(simulator_with_mode(sim, OrderSimulationMode::Enforce)), - true, - ); + let validator = build_1271_validator(signature_validator, Some(order_simulator(sim)), true); let result = validator .validate_and_construct_order( make_1271_order_creation(), @@ -3204,27 +3059,23 @@ mod tests { #[tokio::test] async fn simulator_is_not_invoked_for_non_eip1271_orders() { - // Build an EOA (Eip712) order and a simulator configured in enforce mode. - // The simulator should NOT be called — only EIP-1271 orders go through - // the simulation path. + // Only EIP-1271 orders go through the simulation path; verify that an + // EOA order does not invoke the simulator. let mut signature_validator = MockSignatureValidating::new(); signature_validator .expect_validate_signature_and_get_additional_gas() .times(0); let mut sim = MockOrderSimulating::new(); sim.expect_simulate().times(0); - let validator = build_1271_validator( - signature_validator, - Some(simulator_with_mode(sim, OrderSimulationMode::Enforce)), - false, - ); + let validator = + build_1271_validator(signature_validator, Some(order_simulator(sim)), false); let eoa_order = OrderCreation { signature: Signature::Eip712(EcdsaSignature::non_zero()), ..make_1271_order_creation() }; // Ignore the final result (it will fail WrongOwner/etc. later in the - // pipeline — we only care that the sim was not invoked). + // pipeline - we only care that the sim was not invoked). let _ = validator .validate_and_construct_order( eoa_order, From 2a1e8b05b1e8932668783570d72419cd81e7bf73 Mon Sep 17 00:00:00 2001 From: squadgazzz Date: Thu, 7 May 2026 19:18:41 +0100 Subject: [PATCH 154/154] Inline SimulationOutcome into Result<(), OrderSimulationError> The enum was a near-1:1 copy of the underlying Result with the timeout case folded in. Returning the Result directly drops the enum and the single-caller run_order_simulation_only helper. --- crates/shared/src/order_validation.rs | 117 +++++++------------------- 1 file changed, 31 insertions(+), 86 deletions(-) diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 4b6f007f88..aa11fb58b9 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -59,106 +59,58 @@ pub struct OrderSimulator { pub timeout: Duration, } -#[derive(Debug)] -enum SimulationOutcome { - Pass, - Reverted { - reason: String, - tenderly_url: Option, - }, - Infra(anyhow::Error), -} - -/// Runs a simulation under the given timeout and folds the -/// timeout / revert / infra cases into a single [`SimulationOutcome`]. +/// Runs the simulation under the configured timeout, folding the timeout +/// case into [`OrderSimulationError::Infra`]. async fn timed_simulation( - sim: &dyn OrderSimulating, + config: &OrderSimulator, order: &Order, full_app_data: String, - timeout: Duration, -) -> SimulationOutcome { - let Ok(simulation_result) = - tokio::time::timeout(timeout, sim.simulate(order, full_app_data)).await - else { - return SimulationOutcome::Infra(anyhow!("order simulation timeout")); - }; - - match simulation_result { - Ok(()) => SimulationOutcome::Pass, - Err(OrderSimulationError::Reverted { - reason, - tenderly_url, - }) => SimulationOutcome::Reverted { - reason, - tenderly_url, - }, - Err(OrderSimulationError::Infra(err)) => SimulationOutcome::Infra(err), +) -> Result<(), OrderSimulationError> { + match tokio::time::timeout( + config.timeout, + config.simulator.simulate(order, full_app_data), + ) + .await + { + Err(_) => Err(OrderSimulationError::Infra(anyhow!( + "order simulation timeout" + ))), + Ok(res) => res, } } -/// Logs the simulation result alongside the signature-check outcome. -/// Cases that disagree (sig pass + sim revert, sig fail + sim pass) are -/// surfaced as warnings, infra errors are logged separately. +/// Logs the simulation result alongside the signature outcome. Disagreements +/// (signature pass + simulation revert, or vice versa) and infra errors are +/// surfaced as warnings; agreement is silent. fn log_simulation_outcome( signature: &Result, - simulation: &SimulationOutcome, + simulation: &Result<(), OrderSimulationError>, order_uid: OrderUid, ) { match (signature, simulation) { ( Ok(_), - SimulationOutcome::Reverted { + Err(OrderSimulationError::Reverted { reason, tenderly_url, - }, + }), ) => tracing::warn!( ?order_uid, ?reason, ?tenderly_url, "order simulation disagreement: signature passed, simulation reverted", ), - (Err(SignatureValidationError::Invalid), SimulationOutcome::Pass) => tracing::warn!( + (Err(SignatureValidationError::Invalid), Ok(())) => tracing::warn!( ?order_uid, "order simulation disagreement: signature invalid, simulation passed", ), - (_, SimulationOutcome::Infra(err)) => { + (_, Err(OrderSimulationError::Infra(err))) => { tracing::warn!(?order_uid, ?err, "order simulation infra error") } _ => {} } } -async fn run_order_simulation_only( - config: &OrderSimulator, - preview_order: &Order, - full_app_data: String, -) { - let outcome = timed_simulation( - config.simulator.as_ref(), - preview_order, - full_app_data, - config.timeout, - ) - .await; - match &outcome { - SimulationOutcome::Reverted { - reason, - tenderly_url, - } => tracing::warn!( - order_uid = %preview_order.metadata.uid, - ?reason, - ?tenderly_url, - "order simulation reverted (signature check skipped)", - ), - SimulationOutcome::Infra(err) => tracing::warn!( - order_uid = %preview_order.metadata.uid, - ?err, - "order simulation infra error (signature check skipped)", - ), - SimulationOutcome::Pass => {} - } -} - #[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait::async_trait] pub trait OrderValidating: Send + Sync { @@ -524,7 +476,10 @@ impl OrderValidator { ) -> Result { if self.eip1271_skip_creation_validation { if let Some(config) = &self.order_simulator { - run_order_simulation_only(config, preview_order, full_app_data).await; + let simulation = timed_simulation(config, preview_order, full_app_data).await; + // No signature outcome to compare against, so synthesize a + // signature-pass: only simulation reverts and infra errors log. + log_simulation_outcome(&Ok(0), &simulation, preview_order.metadata.uid); } return Ok(0u64); } @@ -557,22 +512,12 @@ impl OrderValidator { // Shadow mode: the simulation runs for observability only. Disagreements // are logged below and never affect the return value. The enforce-mode - // follow-up will consume `simulation_outcome` here to reject orders - // whose simulation reverts. - let simulation_fut = timed_simulation( - config.simulator.as_ref(), - preview_order, - full_app_data, - config.timeout, - ); - - let (signature_res, simulation_outcome) = tokio::join!(signature_fut, simulation_fut); + // follow-up will consume `simulation` here to reject orders whose + // simulation reverts. + let simulation_fut = timed_simulation(config, preview_order, full_app_data); + let (signature_res, simulation) = tokio::join!(signature_fut, simulation_fut); - log_simulation_outcome( - &signature_res, - &simulation_outcome, - preview_order.metadata.uid, - ); + log_simulation_outcome(&signature_res, &simulation, preview_order.metadata.uid); signature_res.map_err(|err| match err { SignatureValidationError::Invalid => ValidationError::InvalidEip1271Signature(hash),