Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
cb15a18
feat: generalized wrappers
kaze-cow Sep 18, 2025
1c3c0c3
fix: improve docker builds
kaze-cow Sep 19, 2025
ab75508
everything works
kaze-cow Sep 19, 2025
2250d08
its close
kaze-cow Sep 24, 2025
6c6340c
successful placement of wrapped order!
kaze-cow Sep 24, 2025
c24b371
undo cargo changes
kaze-cow Sep 24, 2025
d248fe4
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Sep 24, 2025
b9af257
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Sep 25, 2025
4214939
add gpv2wrapper info
kaze-cow Sep 25, 2025
07ff1a1
wrapper_data is close to working but not getting sent somewhere
kaze-cow Sep 26, 2025
fa3a650
all working
kaze-cow Sep 26, 2025
4e26f52
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 6, 2025
2b842d4
remove unnecessary wrapper file
kaze-cow Oct 6, 2025
0c5cbb6
clean out superfluous changes
kaze-cow Oct 7, 2025
2b27533
format
kaze-cow Oct 7, 2025
83476ca
fix issues, now working
kaze-cow Oct 7, 2025
bf69f8e
Update crates/solvers/src/api/routes/solve/dto/solution.rs
kaze-cow Oct 8, 2025
56e6118
changes in response to comments
kaze-cow Oct 8, 2025
866db4b
Update crates/driver/src/infra/solver/dto/solution.rs
kaze-cow Oct 8, 2025
acac299
removed all tuple structures
kaze-cow Oct 8, 2025
631ab9f
Merge branch 'feat/generalized-wrapper' of https://github.com/cowprot…
kaze-cow Oct 8, 2025
533a572
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 8, 2025
aa4ff59
fix compile
kaze-cow Oct 8, 2025
657d19a
just fmt
kaze-cow Oct 8, 2025
0a83903
fix e2e
kaze-cow Oct 8, 2025
c41442e
fix one more test
kaze-cow Oct 8, 2025
a8d7efc
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 13, 2025
20a743f
test e2e verify works
kaze-cow Oct 13, 2025
65184fa
fix clippy
kaze-cow Oct 13, 2025
02b58ab
remove settlement from the upstream wrapper encoding
kaze-cow Oct 17, 2025
e0f3a8e
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 17, 2025
a97d588
update default wrapper address
kaze-cow Oct 17, 2025
fd7a061
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 22, 2025
0d64a6f
update encoding to include wrapperData length
kaze-cow Oct 22, 2025
87cdc3f
add `is_omittable` to appData
kaze-cow Oct 22, 2025
cfd68dc
update the test to use new wrapper parse
kaze-cow Oct 22, 2025
f3187c2
Update crates/contracts/build.rs
kaze-cow Oct 23, 2025
8ae48f6
Update crates/app-data/src/app_data.rs
kaze-cow Oct 23, 2025
4830313
fix feedbacks
kaze-cow Oct 23, 2025
a281238
update internal data structures to not be Optional Vecs
kaze-cow Oct 23, 2025
ead79d5
remove unnecessary comment
kaze-cow Oct 23, 2025
e8b04a2
restructure flashloans and wrappers structuring in encoding.rs
kaze-cow Oct 23, 2025
09be947
refactor
kaze-cow Oct 23, 2025
c88c44d
just fmt
kaze-cow Oct 23, 2025
0596789
Merge branch 'main' into feat/generalized-wrapper
kaze-cow Oct 23, 2025
ef7b2bd
Merge branch 'main' into feat/generalized-wrapper
kaze-cow Oct 24, 2025
bbd8097
fix so that settle_calldata always is appended with the auction id an…
kaze-cow Oct 24, 2025
f7601b6
Merge branch 'main' into feat/generalized-wrapper
kaze-cow Oct 27, 2025
d94a5f0
Update crates/solvers/src/domain/solution.rs
kaze-cow Oct 27, 2025
ade0037
Update crates/driver/src/domain/competition/solution/mod.rs
kaze-cow Oct 27, 2025
4601ec1
Update crates/driver/src/domain/competition/solution/encoding.rs
kaze-cow Oct 27, 2025
a203947
Update crates/driver/src/domain/competition/solution/encoding.rs
kaze-cow Oct 27, 2025
2696ee8
Update crates/driver/src/domain/competition/solution/encoding.rs
kaze-cow Oct 27, 2025
abb3a44
just format abi files
kaze-cow Oct 27, 2025
a80ad6b
fix feedbacks and compile errors
kaze-cow Oct 27, 2025
afe17c5
Merge branch 'main' into feat/generalized-wrapper
kaze-cow Oct 27, 2025
e191844
Merge branch 'main' into feat/generalized-wrapper
kaze-cow Oct 27, 2025
de269d7
fix test failures
kaze-cow Oct 27, 2025
7a2ba66
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 30, 2025
447ac7c
add e2e test for wrapper
kaze-cow Oct 30, 2025
e8d13a9
implement feedback
kaze-cow Oct 30, 2025
f392fc1
add safety condition
kaze-cow Oct 30, 2025
e4d4216
final changes
kaze-cow Oct 30, 2025
6e810ca
fix
kaze-cow Oct 31, 2025
1f356b0
final test cleanups
kaze-cow Oct 31, 2025
525224a
Merge remote-tracking branch 'origin/main' into feat/generalized-wrapper
kaze-cow Oct 31, 2025
2edbd05
make test more reliable
kaze-cow Oct 31, 2025
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
30 changes: 30 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions configs/local/driver.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
app-data-fetching-enabled = true
orderbook-url = "http://orderbook"
tx-gas-limit = "45000000"

[[solver]]
Expand Down
22 changes: 22 additions & 0 deletions crates/app-data/src/app_data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::{AppDataHash, Hooks, app_data_hash::hash_full_app_data},
anyhow::{Context, Result, anyhow},
bytes_hex::BytesHex,
number::serialization::HexOrDecimalU256,
primitive_types::{H160, U256},
serde::{Deserialize, Deserializer, Serialize, Serializer, de},
Expand All @@ -21,6 +22,7 @@ pub struct ValidatedAppData {
pub protocol: ProtocolAppData,
}

#[serde_as]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "test_helpers"), derive(Serialize))]
#[serde(rename_all = "camelCase")]
Expand All @@ -32,6 +34,8 @@ pub struct ProtocolAppData {
#[serde(default)]
pub partner_fee: PartnerFees,
pub flashloan: Option<Flashloan>,
#[serde(default)]
pub wrappers: Vec<WrapperCall>,
}

/// Contains information to hint at how a solver could make
Expand All @@ -57,6 +61,23 @@ pub struct Flashloan {
pub amount: U256,
}

/// Contains information about wrapper contracts
#[serde_as]
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "test_helpers"), derive(Serialize))]
#[serde(rename_all = "camelCase")]
pub struct WrapperCall {
/// The address of the wrapper contract.
pub address: H160,
/// Additional calldata to be passed to the wrapper contract.
#[serde_as(as = "BytesHex")]
pub data: Vec<u8>,
/// Declares whether this wrapper (and its data) needs to be included
/// unmodified in a solution containing this order.
#[serde(default)]
pub is_omittable: bool,
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "test_helpers"), derive(Serialize))]
pub struct ReplacedOrder {
Expand Down Expand Up @@ -440,6 +461,7 @@ impl From<BackendAppData> for ProtocolAppData {
fn from(value: BackendAppData) -> Self {
Self {
hooks: value.hooks,
wrappers: Vec::new(),
signer: None,
replaced_order: None,
partner_fee: PartnerFees::default(),
Expand Down
41 changes: 41 additions & 0 deletions crates/contracts/artifacts/ICowWrapper.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"abi": [
{
"type": "function",
"name": "parseWrapperData",
"inputs": [
{
"name": "wrapperData",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "remainingWrapperData",
"type": "bytes",
"internalType": "bytes"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "wrappedSettle",
"inputs": [
{
"name": "settleData",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "wrapperData",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]
}
2 changes: 2 additions & 0 deletions crates/contracts/src/alloy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ crate::bindings!(
}
);

crate::bindings!(ICowWrapper);

// Only used in <github.com/gnosis/solvers>
crate::bindings!(
Permit2,
Expand Down
7 changes: 7 additions & 0 deletions crates/driver/src/domain/competition/order/app_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ impl AppData {
Self::Full(data) => data.protocol.flashloan.as_ref(),
}
}

pub fn wrappers(&self) -> &[app_data::WrapperCall] {
match self {
Self::Hash(_) => &[],
Self::Full(data) => &data.protocol.wrappers,
}
}
}

impl From<[u8; APP_DATA_LEN]> for AppData {
Expand Down
128 changes: 104 additions & 24 deletions crates/driver/src/domain/competition/solution/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use {
util::Bytes,
},
allowance::Allowance,
alloy::sol_types::SolCall,
contracts::alloy::{FlashLoanRouter::LoanRequest, WETH9},
ethcontract::H160,
ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy},
Expand All @@ -32,6 +33,8 @@ pub enum Error {
// TODO: remove when contracts are deployed everywhere
#[error("flashloan support disabled")]
FlashloanSupportDisabled,
#[error("both wrappers and flashloans cannot be encoded in the same auction")]
FlashloanWrappersIncompatible,
}

pub fn tx(
Expand Down Expand Up @@ -206,6 +209,10 @@ 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()
.settle(
Expand All @@ -221,36 +228,20 @@ pub fn tx(
.calldata()
.to_vec();

// Encode the auction id into the calldata
// Append auction ID to settlement calldata
settle_calldata.extend(auction.id().ok_or(Error::MissingAuctionId)?.to_be_bytes());

// Target and calldata depend on whether a flashloan is used
let (to, calldata) = if solution.flashloans.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)
} else {
(
contracts.settlement().address().into_legacy().into(),
settle_calldata,
)
} else {
let router = contracts
.flashloan_router()
.ok_or(Error::FlashloanSupportDisabled)?;

let flashloans = solution
.flashloans
.values()
.map(|flashloan| LoanRequest::Data {
amount: flashloan.amount.0.into_alloy(),
borrower: flashloan.protocol_adapter.0.into_alloy(),
lender: flashloan.liquidity_provider.0.into_alloy(),
token: flashloan.token.0.0.into_alloy(),
})
.collect();

let calldata = router
.flashLoanAndSettle(flashloans, settle_calldata.into())
.calldata()
.to_vec();
(router.address().into_legacy().into(), calldata)
};

Ok(eth::Tx {
Expand All @@ -262,6 +253,95 @@ pub fn tx(
})
}

/// Encodes a settlement transaction that uses flashloans.
///
/// Takes the base settlement calldata and wraps it in a flashLoanAndSettle call
/// to the flashloan router contract.
///
/// Returns (router_address, flashloan_calldata)
fn encode_flashloan_settlement(
solution: &super::Solution,
contracts: &infra::blockchain::Contracts,
settle_calldata: Vec<u8>,
) -> Result<(eth::Address, Vec<u8>), Error> {
// Get flashloan router contract
let router = contracts
.flashloan_router()
.ok_or(Error::FlashloanSupportDisabled)?;

// Convert flashloans to LoanRequest format
let flashloans = solution
.flashloans
.values()
.map(|flashloan| LoanRequest::Data {
amount: flashloan.amount.0.into_alloy(),
borrower: flashloan.protocol_adapter.0.into_alloy(),
lender: flashloan.liquidity_provider.0.into_alloy(),
token: flashloan.token.0.0.into_alloy(),
})
.collect();

// Wrap settlement in flashLoanAndSettle call
let calldata = router
.flashLoanAndSettle(flashloans, settle_calldata.into())
.calldata()
.to_vec();

Ok((router.address().into_legacy().into(), 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.0.as_bytes());
}

// 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
}

pub fn liquidity_interaction(
liquidity: &Liquidity,
slippage: &slippage::Parameters,
Expand Down
Loading
Loading