Skip to content
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6897fec
Add EIP-1271 shadow simulator scaffolding
squadgazzz Apr 21, 2026
4d63688
Fix clippy and align SimulationFailed with tuple convention
squadgazzz Apr 21, 2026
7eb008b
Thread optional EIP-1271 shadow simulator into OrderValidator
squadgazzz Apr 21, 2026
3d074e4
Extract shadow sim timeout constant and rename HTTP error code
squadgazzz Apr 21, 2026
d68cb90
Integrate EIP-1271 shadow simulation in OrderValidator
squadgazzz Apr 21, 2026
f0af347
Add shadow sim mode and timeout to orderbook config
squadgazzz Apr 21, 2026
bd51ed8
Wire EIP-1271 shadow simulator through orderbook runtime
squadgazzz Apr 21, 2026
5c6e8d2
Rename shadow-sim types to drop the shadow prefix
squadgazzz Apr 21, 2026
557ae35
Clean up remaining 'shadow' references in docs and locals
squadgazzz Apr 21, 2026
6e7ee7a
Nits
squadgazzz Apr 21, 2026
1733fb2
Group EIP-1271 sim deps into Eip1271SimConfig on OrderValidator
squadgazzz Apr 21, 2026
af31a39
Rename Eip1271Simulator trait to Eip1271Simulating; promote SimConfig…
squadgazzz Apr 21, 2026
65a0290
Collapse build_1271_validator args to take Option<Eip1271Simulator>
squadgazzz Apr 21, 2026
a37c7a0
Move DEFAULT_EIP1271_SIM_TIMEOUT into test module
squadgazzz Apr 21, 2026
9c93de6
Simplify Eip1271Simulator doc comment
squadgazzz Apr 21, 2026
f5ddba5
Rename cheap→signature in sim metrics; merge sim_only_total into total
squadgazzz Apr 21, 2026
cebd99b
Rename duration_seconds histogram to simulation_time
squadgazzz Apr 21, 2026
67a4d32
Drop owner from sim logs; OrderUid already encodes it
squadgazzz Apr 21, 2026
2fbf9f0
Split sim disagreement log into explicit arms instead of an empty-str…
squadgazzz Apr 21, 2026
a781c44
Use Debug formatting (?) in sim logs to match project convention
squadgazzz Apr 21, 2026
d11442e
Expand sim abbreviation to simulation across types, fields, metrics, …
squadgazzz Apr 21, 2026
eca5c61
Simplify Eip1271Simulating doc and scope its mock to cfg(test) only
squadgazzz Apr 21, 2026
5407c8b
Add Disabled mode for order-creation EIP-1271 simulation
squadgazzz Apr 21, 2026
eed5990
Disabled by default
squadgazzz Apr 21, 2026
2785108
Align default test with Disabled as the default simulation mode
squadgazzz Apr 21, 2026
7281ded
Redundant comment
squadgazzz Apr 21, 2026
3525684
Assert simulator is never invoked for non-EIP-1271 orders
squadgazzz Apr 21, 2026
191d7ff
Consolidate EIP-1271 signature/simulation quadrant tests into a matrix
squadgazzz Apr 21, 2026
e62217c
Use .label() on outcomes instead of hardcoded strings in sim logs
squadgazzz Apr 21, 2026
4c25c46
Address Claude-review feedback on PR 4355
squadgazzz Apr 21, 2026
250f5da
Convert order_simulator::Error via From impl for ? ergonomics
squadgazzz Apr 22, 2026
c51a522
Review comments
squadgazzz Apr 22, 2026
7bd2592
Nit
squadgazzz Apr 22, 2026
ea11e37
Add SignatureCheck::new constructor
squadgazzz Apr 22, 2026
ebfd4a9
Pull timeout handling out of simulation_fut in run_eip1271_with_signa…
squadgazzz Apr 22, 2026
06c478a
Merge branch 'main' into feat/orderbook-eip1271-shadow-sim
squadgazzz Apr 22, 2026
490bcdc
Address jose review nits
squadgazzz Apr 27, 2026
fbe765c
Comment
squadgazzz Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions crates/configs/src/orderbook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use {
std::{
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
path::Path,
time::Duration,
},
};

Expand Down Expand Up @@ -59,6 +60,31 @@ pub struct OrderSimulationConfig {
/// URL.
#[serde(default)]
pub tenderly: Option<crate::simulator::TenderlyConfig>,

/// Mode for the EIP-1271 order 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,
}

/// Mode for the EIP-1271 order simulation at order creation.
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Eip1271SimulationMode {
Shadow,
Enforce,
#[default]
Disabled,
}
Comment on lines +76 to +84
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be part of either the configs crate or the order simulator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is in configs, this is the enum you're commenting on. The copy in shared::order_validation is only there because OrderValidator lives in shared and shared can't depend on configs, so we mirror plus convert at the call site in run.rs. Once Martin's simulator refactor lands and OrderSimulator moves out of orderbook, we can probably collapse both into one enum.


fn default_eip1271_simulation_timeout() -> Duration {
Duration::from_secs(2)
}

/// Top-level orderbook service configuration.
Expand Down Expand Up @@ -228,6 +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_simulation_mode: Default::default(),
eip1271_simulation_timeout: std::time::Duration::from_secs(2),
}),
hide_competition_before_deadline: false,
}
Expand Down Expand Up @@ -438,4 +466,36 @@ mod tests {
);
assert_eq!(config.http_client.timeout, deserialized.http_client.timeout)
}

#[test]
fn parses_simulation_mode_default() {
let toml = r#"
gas-limit = "0x1000000"
"#;
Comment thread
squadgazzz marked this conversation as resolved.
Outdated
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));
}

#[test]
fn parses_simulation_mode_enforce() {
let toml = r#"
gas-limit = "0x1000000"
eip1271-simulation-mode = "enforce"
eip1271-simulation-timeout = "5s"
"#;
Comment thread
squadgazzz marked this conversation as resolved.
Outdated
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));
}

#[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);
}
}
5 changes: 5 additions & 0 deletions crates/orderbook/src/api/post_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ impl IntoResponse for ValidationErrorWrapper {
),
)
.into_response(),
ValidationError::SimulationFailed(reason) => (
StatusCode::BAD_REQUEST,
error("Eip1271SimulationFailed", reason),
)
.into_response(),
ValidationError::InsufficientBalance => (
StatusCode::BAD_REQUEST,
error(
Expand Down
54 changes: 54 additions & 0 deletions crates/orderbook/src/eip1271_simulation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use {
crate::order_simulator::{self, OrderSimulator},
async_trait::async_trait,
model::order::Order,
shared::order_validation::{Eip1271Simulating, Eip1271SimulationError},
std::sync::Arc,
};

/// Adapter exposing `OrderSimulator` via the
/// `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
/// adapter can be deleted.
pub struct OrderSimulatorAdapter {
inner: Arc<OrderSimulator>,
}

impl OrderSimulatorAdapter {
pub fn new(inner: Arc<OrderSimulator>) -> Self {
Self { inner }
}
}

#[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)?;
match result.error {
None => Ok(()),
Some(reason) => Err(Eip1271SimulationError::Reverted {
reason,
tenderly_url: result.tenderly_url,
}),
}
Comment thread
squadgazzz marked this conversation as resolved.
}
}

fn map_simulator_err(err: order_simulator::Error) -> Eip1271SimulationError {
Comment thread
squadgazzz marked this conversation as resolved.
Outdated
match err {
order_simulator::Error::Other(e) | order_simulator::Error::MalformedInput(e) => {
Eip1271SimulationError::Infra(e)
}
}
}
1 change: 1 addition & 0 deletions crates/orderbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod app_data;
pub mod arguments;
pub mod database;
pub mod dto;
mod eip1271_simulation;
mod ipfs;
mod ipfs_app_data;
pub mod order_simulator;
Expand Down
90 changes: 59 additions & 31 deletions crates/orderbook/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ use {
},
shared::{
order_quoting::{self, OrderQuoter},
order_validation::{OrderValidPeriodConfiguration, OrderValidator},
order_validation::{
Eip1271SimulationMode,
Eip1271Simulator,
OrderValidPeriodConfiguration,
OrderValidator,
},
},
simulator::swap_simulator::SwapSimulator,
std::{future::Future, net::SocketAddr, sync::Arc, time::Duration},
Expand Down Expand Up @@ -375,6 +380,58 @@ pub async fn run(config: Configuration) {
let chainalysis_oracle = ChainalysisOracle::Instance::deployed(&web3.provider)
.await
.ok();

let (order_simulator, eip1271_simulator) = match config.order_simulation {
Some(sim_config) => {
let tenderly: Option<Box<dyn simulator::tenderly::Api>> =
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 mode = match sim_config.eip1271_simulation_mode {
configs::orderbook::Eip1271SimulationMode::Shadow => {
Some(Eip1271SimulationMode::Shadow)
}
configs::orderbook::Eip1271SimulationMode::Enforce => {
Some(Eip1271SimulationMode::Enforce)
}
configs::orderbook::Eip1271SimulationMode::Disabled => None,
};
let eip1271_simulator = mode.map(|mode| {
let simulator: Arc<dyn shared::order_validation::Eip1271Simulating> = 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 order_validator = Arc::new(OrderValidator::new(
native_token.clone(),
Arc::new(order_validation::banned::Users::new(
Expand All @@ -389,6 +446,7 @@ pub async fn run(config: Configuration) {
optimal_quoter.clone(),
balance_fetcher,
signature_validator,
eip1271_simulator,
Arc::new(postgres_write.clone()),
config.order_validation.max_limit_orders_per_user,
code_fetcher,
Expand All @@ -411,36 +469,6 @@ pub async fn run(config: Configuration) {
ipfs,
));

let order_simulator = if let Some(config) = config.order_simulation {
let tenderly: Option<Box<dyn simulator::tenderly::Api>> =
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(),
Expand Down
Loading
Loading