Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 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
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
5 changes: 5 additions & 0 deletions Cargo.lock

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

7 changes: 7 additions & 0 deletions crates/app-data/src/app_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ impl Root {
backend: None,
}
}

pub fn wrappers(&self) -> &[WrapperCall] {
self.metadata
.as_ref()
.map(|metadata| metadata.wrappers.as_slice())
.unwrap_or_default()
}
}

/// Caches whether a given app data document contains wrappers, keyed by
Expand Down
27 changes: 25 additions & 2 deletions crates/configs/src/orderbook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use {
price_estimation::PriceEstimation,
shared::SharedConfig,
},
alloy::primitives::Address,
alloy::primitives::{Address, U256},
anyhow::anyhow,
chrono::{DateTime, Utc},
serde::{Deserialize, Serialize},
Expand Down Expand Up @@ -112,6 +112,11 @@ pub struct Configuration {
/// 1inch, quote verification, balance overrides, etc.).
#[serde(default)]
pub price_estimation: PriceEstimation,

/// Configures if the gas limit for orders simulation. If set to None, the
/// endpoint is disabled.
#[serde(default)]
pub order_simulation_gas_limit: Option<U256>,
}

impl Configuration {
Expand Down Expand Up @@ -142,8 +147,10 @@ pub mod test_util {
default_bind_address,
native_price::NativePriceConfig,
},
price_estimation::PriceEstimation,
test_util::TestDefault,
},
alloy::primitives::U256,
std::path::Path,
};

Expand Down Expand Up @@ -193,7 +200,17 @@ pub mod test_util {
database: TestDefault::test_default(),
http_client: Default::default(),
order_quoting: TestDefault::test_default(),
price_estimation: TestDefault::test_default(),
price_estimation: PriceEstimation {
balance_overrides: crate::price_estimation::BalanceOverridesConfig {
autodetect: true,
..Default::default()
},
..TestDefault::test_default()
},
// Enable order simulation for testing
order_simulation_gas_limit: Some(
U256::try_from(16777215).expect("u64 can be converted to U256"),
),
}
}
}
Expand All @@ -218,6 +235,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 Expand Up @@ -252,6 +270,10 @@ mod tests {
assert_eq!(config.unsupported_tokens.len(), 1);
assert_eq!(config.banned_users.addresses.len(), 1);
assert!(config.eip1271_skip_creation_validation);
assert_eq!(
config.order_simulation_gas_limit,
Some(U256::from(123456789u64))
);

assert!(matches!(
config.order_validation.same_tokens_policy,
Expand Down Expand Up @@ -354,6 +376,7 @@ mod tests {
database: TestDefault::test_default(),
http_client: Default::default(),
price_estimation: Default::default(),
order_simulation_gas_limit: Default::default(),
};

let serialized = toml::to_string_pretty(&config).unwrap();
Expand Down
73 changes: 9 additions & 64 deletions crates/driver/src/domain/competition/solution/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ use {
infra::{self, solver::ManageNativeToken},
},
allowance::Allowance,
alloy::{
primitives::{Address, Bytes, FixedBytes, U256},
sol_types::SolCall,
},
alloy::primitives::{Address, Bytes, FixedBytes, U256},
contracts::alloy::{FlashLoanRouter::LoanRequest, WETH9},
eth_domain_types::{self as eth, Ether, allowance},
itertools::Itertools,
Expand Down Expand Up @@ -209,9 +206,6 @@ pub fn tx(
interactions.push(unwrap(native_unwrap, contracts.weth()));
}

let has_flashloans = !solution.flashloans.is_empty();
let has_wrappers = !solution.wrappers.is_empty();

// Encode the base settlement calldata
let mut settle_calldata = contracts
.settlement()
Expand All @@ -230,21 +224,24 @@ pub fn tx(

// Append auction ID to settlement calldata
settle_calldata.extend(auction.id().ok_or(Error::MissingAuctionId)?.to_be_bytes());
let has_flashloans = !solution.flashloans.is_empty();
let has_wrappers = !solution.wrappers.is_empty();

let (to, calldata) = if has_flashloans && has_wrappers {
return Err(Error::FlashloanWrappersIncompatible);
} else if has_flashloans {
encode_flashloan_settlement(solution, contracts, settle_calldata)?
} else if has_wrappers {
encode_wrapper_settlement(solution, settle_calldata)
simulator::encoding::encode_wrapper_settlement(&solution.wrappers, settle_calldata.into())
.expect("wrappers is not empty")
} else {
(*contracts.settlement().address(), settle_calldata)
(*contracts.settlement().address(), settle_calldata.into())
};

Ok(eth::Tx {
from: solution.solver().address(),
to,
input: calldata.into(),
input: calldata,
value: Ether::zero(),
access_list: Default::default(),
})
Expand All @@ -260,7 +257,7 @@ fn encode_flashloan_settlement(
solution: &super::Solution,
contracts: &infra::blockchain::Contracts,
settle_calldata: Vec<u8>,
) -> Result<(eth::Address, Vec<u8>), Error> {
) -> Result<(eth::Address, Bytes), Error> {
// Get flashloan router contract
let router = contracts
.flashloan_router()
Expand All @@ -284,59 +281,7 @@ fn encode_flashloan_settlement(
.calldata()
.to_vec();

Ok((*router.address(), calldata))
}

/// Encodes a settlement transaction that uses wrapper contracts.
///
/// Takes the base settlement calldata and wraps it in a wrappedSettleCall
/// with encoded wrapper metadata. Since wrappers are a chain, the wrapper
/// address to call is also processed by this function.
///
/// Returns (first_wrapper_address, wrapped_calldata)
fn encode_wrapper_settlement(
solution: &super::Solution,
settle_calldata: Vec<u8>,
) -> (eth::Address, Vec<u8>) {
// Encode wrapper metadata
let wrapper_data = encode_wrapper_data(&solution.wrappers);

// Create wrappedSettleCall
let calldata = contracts::alloy::ICowWrapper::ICowWrapper::wrappedSettleCall {
settleData: settle_calldata.into(),
wrapperData: wrapper_data.into(),
}
.abi_encode();

(solution.wrappers[0].address, calldata)
}

/// Encodes wrapper metadata for wrapper settlement calls.
///
/// The format is:
/// - For wrappers after the first: 20 bytes (address)
/// - For each wrapper: 2 bytes (data length as u16 in native endian) + data
///
/// More information about wrapper encoding:
/// https://www.notion.so/cownation/Generalized-Wrapper-2798da5f04ca8095a2d4c56b9d17134e?source=copy_link#2858da5f04ca807980bbf7f845354120
///
/// Note: The first wrapper address is omitted from the encoded data since it's
/// already used as the transaction target.
fn encode_wrapper_data(wrappers: &[super::WrapperCall]) -> Vec<u8> {
let mut wrapper_data = Vec::new();

for (index, w) in wrappers.iter().enumerate() {
// Skip first wrapper's address (it's the transaction target)
if index != 0 {
wrapper_data.extend(w.address.as_slice());
}

// Encode data length as u16 in native endian, then the data itself
wrapper_data.extend((w.data.len() as u16).to_be_bytes().to_vec());
wrapper_data.extend(w.data.clone());
}

wrapper_data
Ok((*router.address(), calldata.into()))
}

pub fn liquidity_interaction(
Expand Down
8 changes: 1 addition & 7 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use {
itertools::Itertools,
num::{BigRational, One},
number::conversions::{big_rational_to_u256, u256_to_big_int, u256_to_big_rational},
simulator::{self, Simulator},
simulator::{self, Simulator, encoding::WrapperCall},
solvers_dto::solution::Flashloan,
std::{
collections::{BTreeSet, HashMap, HashSet, hash_map::Entry},
Expand All @@ -41,12 +41,6 @@ pub use {error::Error, interaction::Interaction, settlement::Settlement, trade::

type Prices = HashMap<eth::TokenAddress, eth::U256>;

#[derive(Clone)]
pub struct WrapperCall {
pub address: eth::Address,
pub data: Vec<u8>,
}

// TODO Add a constructor and ensure that the clearing prices are included for
// each trade
/// A solution represents a set of orders which the solver has found an optimal
Expand Down
5 changes: 3 additions & 2 deletions crates/driver/src/infra/solver/dto/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
crate::{
domain::{
self,
competition::{self, solution::WrapperCall},
competition::{self},
liquidity,
},
infra::Solver,
Expand All @@ -15,6 +15,7 @@ use {
DomainSeparator,
order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource},
},
simulator::encoding::WrapperCall,
std::{collections::HashMap, str::FromStr},
};

Expand Down Expand Up @@ -272,7 +273,7 @@ impl Solutions {
}).collect()),
solution.wrappers.iter().cloned().map(|w| WrapperCall {
address: w.address,
data: w.data,
data: w.data.into(),
}).collect(),
)
.map_err(|err| match err {
Expand Down
1 change: 1 addition & 0 deletions crates/e2e/src/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const DEFAULT_FILTERS: &[&str] = &[
"solver=debug",
"solvers=debug",
"orderbook::api::request_summary=off",
"simulator=debug",
];

fn with_default_filters<T>(custom_filters: impl IntoIterator<Item = T>) -> Vec<String>
Expand Down
1 change: 1 addition & 0 deletions crates/e2e/tests/e2e/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod liquidity;
mod liquidity_source_notification;
mod malformed_requests;
mod order_cancellation;
mod order_simulation;
mod parallel_settlement;
mod partial_fill;
mod partially_fillable_balance;
Expand Down
81 changes: 81 additions & 0 deletions crates/e2e/tests/e2e/order_simulation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use {
configs::test_util::TestDefault,
e2e::setup::{API_HOST, OnchainComponents, Services, run_test},
ethrpc::{Web3, alloy::CallBuilderExt},
model::{
order::{OrderCreation, OrderKind},
order_simulator::OrderSimulation,
signature::EcdsaSigningScheme,
},
number::units::EthUnit,
reqwest::StatusCode,
};

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

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

let [solver] = onchain.make_solvers(10u64.eth()).await;
let [trader] = onchain.make_accounts(10u64.eth()).await;
let [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(1_000u64.eth(), 1_000u64.eth())
.await;

onchain
.contracts()
.weth
.deposit()
.from(trader.address())
.value(3u64.eth())
.send_and_watch()
.await
.unwrap();
onchain
.contracts()
.weth
.approve(onchain.contracts().allowance, 3u64.eth())
.from(trader.address())
.send_and_watch()
.await
.unwrap();

let services = Services::new(&onchain).await;
services
.start_protocol_with_args(
configs::autopilot::Configuration::test("test_solver", solver.address()),
configs::orderbook::Configuration::test_default(),
solver,
)
.await;

let order = OrderCreation {
sell_token: *onchain.contracts().weth.address(),
sell_amount: 2u64.eth(),
buy_token: *token.address(),
buy_amount: 1u64.eth(),
valid_to: model::time::now_in_epoch_seconds() + 300,
kind: OrderKind::Buy,
..Default::default()
}
.sign(
EcdsaSigningScheme::Eip712,
&onchain.contracts().domain_separator,
&trader.signer,
);
let uid = services.create_order(&order).await.unwrap();

let client = services.client();
let response = client
.get(format!("{API_HOST}/api/v1/debug/simulation/{uid}"))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let response = response.json::<OrderSimulation>().await.unwrap();
assert_eq!(response.error, None);
}
Loading
Loading