Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
69ca6c1
Move common eth domain types to separate crate
m-sz Mar 12, 2026
c87657c
tombi format
m-sz Mar 12, 2026
fd4a8d6
Split up acess_list and token_amount
m-sz Mar 12, 2026
d2687f8
fix doc-test
m-sz Mar 12, 2026
b3253a5
Merge branch 'main' into eth-domain-types
m-sz Mar 12, 2026
43510bf
fmt
m-sz Mar 12, 2026
8ba638c
Fully qualify ops
m-sz Mar 13, 2026
6bd3879
Merge branch 'main' into eth-domain-types
m-sz Mar 13, 2026
c89ea6a
Move autopilot specific, non fundamental types back
m-sz Mar 13, 2026
ef5c1af
Remove WethAddress in favour of WrappedNativeToken
m-sz Mar 13, 2026
225f55c
Hide internals of {Contact,Token}Address
m-sz Mar 13, 2026
050a35a
Add Deref to WrappedNativeToken
m-sz Mar 13, 2026
607683e
Migrate driver to eth-domain-types
m-sz Mar 12, 2026
8bb68a1
Hide internals of {Contact,Token}Address
m-sz Mar 13, 2026
59cc93a
Remove driver/src/domain/eth
m-sz Mar 13, 2026
f995199
Add domain/blockchain and flashloan files for driver
m-sz Mar 13, 2026
5e277c5
fmt + doc test fix
m-sz Mar 13, 2026
1d90ff6
Factor out code-fetching to separate crate
m-sz Mar 13, 2026
37bc9f7
Simulator
m-sz Mar 13, 2026
eb1c81f
De-duplicate settlement_encoding
m-sz Mar 17, 2026
9f8994b
Merge branch 'main' into simulator-crate
m-sz Mar 17, 2026
43ea033
Removed debug tracing calls
m-sz Mar 17, 2026
da7aeaf
Small fix
m-sz Mar 17, 2026
612fd75
TOML fmt
m-sz Mar 17, 2026
a922bb7
Remove unused files
m-sz Mar 17, 2026
07da382
Fix copy-and-paste error
m-sz Mar 17, 2026
a050340
Remove interactions from Query
m-sz Mar 18, 2026
d57160e
Merge branch 'main' into simulator-crate
m-sz Mar 18, 2026
3e127b0
Update crates/simulator/src/swap_simulator.rs
m-sz Mar 19, 2026
ec6fc0f
Add docs
m-sz Mar 19, 2026
adb1970
Merge branch 'main' into simulator-crate
m-sz Mar 19, 2026
fd07b5b
Merge branch 'main' into simulator-crate
m-sz Mar 19, 2026
0973507
Add correct solver for trade_verification
m-sz Mar 19, 2026
f61af42
Add debug logs
m-sz Mar 19, 2026
2fa8a08
Simulation endpoint
m-sz Mar 17, 2026
e8bb53e
Clippy + tombi
m-sz Mar 18, 2026
6eefd09
in/out amount logic
m-sz Mar 18, 2026
66b2d03
Fix settlement target for wrapper calls
m-sz Mar 19, 2026
cc93e3c
Error code for when feature is not enabled
m-sz Mar 19, 2026
c0759c2
Revert "Add debug logs"
m-sz Mar 19, 2026
5510e0a
Revert contracts changes
m-sz Mar 19, 2026
15675ae
Merge branch 'simulator-crate' into simulation-endpoint
m-sz Mar 19, 2026
eb4d0a6
Revert contracts changes
m-sz Mar 19, 2026
532ef29
Revert contracts changes 2
m-sz Mar 19, 2026
37f3650
Merge branch 'simulator-crate' into simulation-endpoint
m-sz Mar 19, 2026
03c6ecb
Merge branch 'main' into simulation-endpoint
m-sz Mar 20, 2026
0c604e5
Add state overrides to order simulation
m-sz Mar 20, 2026
a746694
Add order simulation files which I forgot to add before
m-sz Mar 20, 2026
c49cac7
ditto
m-sz Mar 20, 2026
3c034ec
Merge branch 'main' into simulation-endpoint
m-sz Mar 20, 2026
8c5d109
Remove e2e test module
m-sz Mar 20, 2026
cfb72f0
clippy
m-sz Mar 20, 2026
3a65bae
fmt
m-sz Mar 20, 2026
c458cbf
Order simulation E2E test
m-sz Mar 20, 2026
b1f0ab5
Fix e2e test balance overrides config
m-sz Mar 20, 2026
5dde228
lint + add missing file
m-sz Mar 20, 2026
7ae0fe6
Remove e2e test debug logs
m-sz Mar 20, 2026
1d68536
Remove leftover comment
m-sz Mar 20, 2026
b652c16
Addressed comments
m-sz Mar 20, 2026
fb18d06
Make simulation gas limit config option a U256
m-sz Mar 20, 2026
e93ddca
Merge branch 'main' into simulation-endpoint
m-sz Mar 20, 2026
3948d51
Add order simulation TestDefault config
m-sz Mar 20, 2026
7760789
OpenAPI spec
m-sz Mar 20, 2026
9278131
Merge branch 'main' into simulation-endpoint
m-sz Mar 20, 2026
ec7cd91
Fix OpenAPI spec
m-sz Mar 20, 2026
9ce5309
Merge branch 'main' into simulation-endpoint
m-sz Mar 30, 2026
2a66f8d
Merge branch 'main' into simulation-endpoint
m-sz Mar 31, 2026
d6ebe8f
Update crates/orderbook/src/order_simulator.rs
m-sz Mar 31, 2026
f12c0b5
Merge branch 'main' into simulation-endpoint
m-sz Mar 31, 2026
61eb2ca
Doc fix
m-sz Mar 31, 2026
37adc8d
Pass through EncodedSwap in the post processing methods
m-sz Mar 31, 2026
ff1a373
Fix HTTP error codes and update OpenApi spec
m-sz Mar 31, 2026
057a7ed
Change private to public link related to wrapper doc
m-sz Mar 31, 2026
28943f7
Rename in/out amount/token of Query to sell and buy
m-sz Mar 31, 2026
88972d4
Add missing docs, introduce control over how trade is encoded
m-sz Mar 31, 2026
1551f37
Add doc for the simulate_order method
m-sz Mar 31, 2026
c6193d6
Clippy
m-sz Mar 31, 2026
401d44d
Merge branch 'main' into simulation-endpoint
m-sz Mar 31, 2026
0068069
unwrap() -> expect() on wrapper encoding
m-sz Mar 31, 2026
b7a4f9f
Tx gas simulation
m-sz Mar 31, 2026
b4f5cd9
Gas estimation impl
m-sz Mar 31, 2026
b9d7702
Factor out state overrides
m-sz Mar 31, 2026
5bb45e6
E2E test plan
m-sz Apr 2, 2026
c06dcdb
tx gas estimation E2E test case
m-sz Apr 7, 2026
e7ace5c
Merge branch 'main' into tx-gas-simulation
m-sz Apr 7, 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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/configs/src/orderbook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ mod tests {
active-order-competition-threshold = 10
unsupported-tokens = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"]
eip1271-skip-creation-validation = true
order-simulation-gas-limit = "123456789"

[banned-users]
addresses = ["0xdead000000000000000000000000000000000000"]
Expand Down
35 changes: 35 additions & 0 deletions crates/e2e/src/setup/colocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,41 @@ uni-v3-node-url = "http://localhost:8545"
}
}

pub async fn start_baseline_solver_with_gas_simulation(
name: String,
account: TestAccount,
weth: Address,
base_tokens: Vec<Address>,
max_hops: usize,
merge_solutions: bool,
settlement: Address,
) -> SolverEngine {
let encoded_base_tokens = encode_base_tokens(base_tokens.clone());
let config_file = config_tmp_file(format!(
r#"
weth = "{weth:?}"
base-tokens = [{encoded_base_tokens}]
max-hops = {max_hops}
max-partial-attempts = 5
native-token-price-estimation-amount = "100000000000000000"
uni-v3-node-url = "http://localhost:8545"
gas-simulation-node-url = "http://localhost:8545"
gas-simulation-settlement = "{settlement:?}"
"#,
));
let endpoint = start_solver(config_file, "baseline".to_string()).await;
SolverEngine {
name,
endpoint,
account,
base_tokens,
merge_solutions,
haircut_bps: 0,
submission_keys: vec![],
forwarder_contract: None,
}
}

async fn start_solver(config_file: TempPath, solver_name: String) -> Url {
let args = vec![
"solvers".to_string(),
Expand Down
188 changes: 188 additions & 0 deletions crates/e2e/tests/e2e/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ use {
providers::Provider,
},
app_data::Hook,
configs::{
autopilot::native_price::NativePriceConfig as AutopilotNativePriceConfig,
native_price_estimators::{NativePriceEstimator, NativePriceEstimators},
order_quoting::{ExternalSolver, OrderQuoting},
test_util::TestDefault,
},
e2e::setup::{
OnchainComponents,
Services,
TIMEOUT,
colocation,
onchain_components,
run_test,
safe::Safe,
Expand Down Expand Up @@ -55,6 +62,12 @@ async fn local_node_quote_verification() {
run_test(quote_verification).await;
}

#[tokio::test]
#[ignore]
async fn local_node_tx_gas_simulation() {
run_test(tx_gas_simulation).await;
}

async fn gas_limit(web3: Web3) {
let mut onchain = OnchainComponents::deploy(web3).await;

Expand Down Expand Up @@ -626,3 +639,178 @@ async fn quote_verification(web3: Web3) {
// sell tokens with a pre-hook
assert!(quote.verified);
}

/// Verifies that enabling `gas-simulation-node-url` on the baseline solver
/// produces a higher `gas_amount` in the quote response than the static
/// estimator, because simulation captures the gas cost of order hooks.
async fn tx_gas_simulation(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 [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(100_000u64.eth(), 100_000u64.eth())
.await;

token.mint(trader.address(), 5u64.eth()).await;
token
.approve(onchain.contracts().allowance, 5u64.eth())
.from(trader.address())
.send_and_watch()
.await
.unwrap();

let counter = contracts::alloy::test::Counter::Instance::deploy(web3.provider.clone())
.await
.unwrap();
let pre_call = counter.incrementCounter("pre".to_string());
let pre_gas = pre_call.estimate_gas().await.unwrap();
let pre_hook = Hook {
target: *counter.address(),
call_data: pre_call.calldata().to_vec(),
gas_limit: pre_gas,
};
let post_call = counter.incrementCounter("post".to_string());
let post_gas = post_call.estimate_gas().await.unwrap();
let post_hook = Hook {
target: *counter.address(),
call_data: post_call.calldata().to_vec(),
gas_limit: post_gas,
};

let quote_request = OrderQuoteRequest {
from: trader.address(),
sell_token: *token.address(),
buy_token: *onchain.contracts().weth.address(),
side: OrderQuoteSide::Sell {
sell_amount: SellAmount::BeforeFee {
value: NonZeroU256::try_from(5u64.eth()).unwrap(),
},
},
app_data: OrderCreationAppData::Full {
full: json!({
"metadata": {
"hooks": {
"pre": [pre_hook],
"post": [post_hook],
},
},
})
.to_string(),
},
..Default::default()
};

let solver_address = solver.address();
let settlement = *onchain.contracts().gp_settlement.address();
let weth = *onchain.contracts().weth.address();

// Phase 1: quote without gas simulation.
let solver_no_sim = colocation::start_baseline_solver(
"test_quoter".to_string(),
solver.clone(),
weth,
vec![],
2,
false,
)
.await;
let driver_no_sim = colocation::start_driver(
onchain.contracts(),
vec![solver_no_sim],
colocation::LiquidityProvider::UniswapV2,
false,
);

let services = Services::new(&onchain).await;
services
.start_api(configs::orderbook::Configuration {
order_quoting: OrderQuoting::test_with_drivers(vec![ExternalSolver::new(
"test_quoter",
"http://localhost:11088/test_quoter",
)]),
..configs::orderbook::Configuration::test_default()
})
.await;
services
.start_autopilot(
None,
configs::autopilot::Configuration {
native_price_estimation: AutopilotNativePriceConfig {
estimators: NativePriceEstimators::new(vec![vec![
NativePriceEstimator::driver(
"test_quoter".to_string(),
"http://localhost:11088/test_quoter".parse().unwrap(),
),
]]),
..AutopilotNativePriceConfig::test_default()
},
..configs::autopilot::Configuration::test("test_quoter", solver_address)
},
)
.await;

wait_for_condition(TIMEOUT, || async {
reqwest::get("http://localhost:11088/test_quoter/healthz")
.await
.is_ok()
})
.await
.expect("driver (no sim) did not start in time");

let gas_no_sim = services
.submit_quote(&quote_request)
.await
.unwrap()
.quote
.gas_amount;

// Phase 2: replace driver with one that has gas simulation enabled.
driver_no_sim.abort();
driver_no_sim.await.ok();

let solver_with_sim = colocation::start_baseline_solver_with_gas_simulation(
"test_quoter".to_string(),
solver,
weth,
vec![],
2,
false,
settlement,
)
.await;
let _driver_with_sim = colocation::start_driver(
onchain.contracts(),
vec![solver_with_sim],
colocation::LiquidityProvider::UniswapV2,
false,
);

wait_for_condition(TIMEOUT, || async {
reqwest::get("http://localhost:11088/test_quoter/healthz")
.await
.is_ok()
})
.await
.expect("driver (with sim) did not start in time");

let gas_with_sim = services
.submit_quote(&quote_request)
.await
.unwrap()
.quote
.gas_amount;

assert!(
gas_with_sim > gas_no_sim,
"simulated gas {gas_with_sim} should exceed static estimate {gas_no_sim}"
);
let hooks_gas = bigdecimal::BigDecimal::from(pre_gas + post_gas);
assert!(
gas_with_sim >= hooks_gas,
"simulated gas {gas_with_sim} should be at least the sum of hook gas limits"
);

println!("gas_no_sim={gas_no_sim}, gas_with_sim={gas_with_sim}");
}
1 change: 1 addition & 0 deletions crates/model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod debug_report;
pub mod fee_policy;
pub mod interaction;
pub mod order;
pub mod order_simulator;
pub mod quote;
pub mod signature;
pub mod solver_competition;
Expand Down
114 changes: 114 additions & 0 deletions crates/model/src/order_simulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use {
alloy_primitives::{Address, B256, U256, map::B256Map},
serde::{Deserialize, Serialize},
serde_with::serde_as,
std::collections::HashMap,
};

/// Tenderly API simulation request
/// https://docs.tenderly.co/reference/api#/operations/simulateTransaction#request-body
#[serde_as]
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct TenderlyRequest {
/// ID of the network on which the simulation is being run.
pub network_id: String,
/// Number of the block to be used for the simulation.
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<u64>,
/// Index of the transaction within the block.
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_index: Option<i64>,
/// Address initiating the transaction.
pub from: Address,
/// The recipient address of the transaction.
pub to: Address,
/// Encoded contract method call data.
#[serde_as(as = "serde_ext::Hex")]
pub input: Vec<u8>,
/// Amount of gas provided for the simulation.
#[serde(skip_serializing_if = "Option::is_none")]
pub gas: Option<u64>,
/// String representation of a number that represents price of the gas in
/// Wei.
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<u64>,
/// Amount of Ether (in Wei) sent along with the transaction.
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<U256>,
#[serde(skip_serializing_if = "Option::is_none")]
pub simulation_type: Option<SimulationType>,
/// Flag indicating whether to save the simulation in dashboard UI.
#[serde(skip_serializing_if = "Option::is_none")]
pub save: Option<bool>,
/// Flag indicating whether to save failed simulation in dashboard UI.
#[serde(skip_serializing_if = "Option::is_none")]
pub save_if_fails: Option<bool>,
/// Flag that enables returning the access list in a response.
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_access_list: Option<bool>,
/// Overrides for a given contract.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_objects: Option<HashMap<Address, StateObject>>,
/// EIP-2930 access list used by the transaction.
#[serde(skip_serializing_if = "Option::is_none")]
pub access_list: Option<Vec<AccessListItem>>,
}

/// EIP-2930 access list used by the transaction.
/// https://docs.tenderly.co/reference/api#/operations/simulateTransaction#response-body:~:text=0x-,access_list,-array%20or%20null
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct AccessListItem {
/// Accessed address
pub address: Address,
/// Accessed storage keys
#[serde(default)]
pub storage_keys: Vec<B256>,
}

/// Overrides for a given contract. In this mapping, the key is the contract
/// address, and the value is an object that contains overrides of nonce, code,
/// balance, or state. https://docs.tenderly.co/reference/api#/operations/simulateTransaction#response-body:~:text=null%2C%22uncles%22%3Anull%7D-,state_objects,-dictionary%5Bstring%2C%20object
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct StateObject {
/// Fake balance to set for the account before executing the call.
#[serde(skip_serializing_if = "Option::is_none")]
pub balance: Option<alloy_primitives::U256>,

/// Fake EVM bytecode to inject into the account before executing the call.
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<alloy_primitives::Bytes>,

/// Fake key-value mapping to override **individual** slots in the account
/// storage before executing the call.
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<B256Map<B256>>,
}

/// Opt for quick, abi, or full simulation API mode.
/// full (default): Detailed decoded output — call trace, function
/// inputs/outputs, state diffs, and logs with Solidity types.
///
/// quick: Raw,
/// minimal output only. Fastest option; no decoding.
///
/// abi: Decoded function
/// inputs/outputs and logs, but no state diffs. Middle ground between quick and
/// full.
///
/// https://docs.tenderly.co/reference/api#/operations/simulateTransaction#response-body:~:text=true-,simulation_type,-string
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SimulationType {
Full,
Quick,
Abi,
}

/// 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, Deserialize, Serialize, Debug)]
pub struct OrderSimulation {
pub tenderly_request: TenderlyRequest,
pub error: Option<String>,
}
Loading
Loading