Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion crates/composable-cow/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ where

let intent = CowIntentBody::V1(CowIntent::Signed(SignedOrder {
order: order_data_to_body(&order_data),
owner: owner.into_array(),
owner,
signature: signature.to_vec(),
}));
// Reserve the exact wire bytes the venue submit and the reconcile
Expand Down
2 changes: 1 addition & 1 deletion crates/composable-cow/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn intent_bytes(order: &GPv2OrderData) -> Vec<u8> {
let order_data = gpv2_to_order_data(order).expect("known markers");
CowIntentBody::V1(CowIntent::Signed(SignedOrder {
order: order_data_to_body(&order_data),
owner: sample_owner().into_array(),
owner: sample_owner(),
signature: hex!("c0ffeec0ffeec0ffee").to_vec(),
}))
.to_bytes()
Expand Down
10 changes: 7 additions & 3 deletions crates/cow-venue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ nexum-sdk = { path = "../nexum-sdk", optional = true }
# submission bodies. Express-declared (not workspace-inherited) so the
# guest build never inherits the native `http-client` feature.
cowprotocol = { version = "0.2.0", default-features = false, optional = true }
alloy-primitives = { workspace = true, optional = true }
# `body` slice: the order body fields are alloy `Address`/`U256`, and
# `borsh` supplies their borsh impls (ruint for `U256`). Optional only
# so the empty `--no-default-features` build stays dependency-free.
alloy-primitives = { workspace = true, features = ["borsh"], optional = true }
alloy-sol-types = { workspace = true, optional = true }
# `adapter` slice: the orderbook REST speaker over the scoped
# wasi:http transport.
Expand All @@ -51,6 +54,7 @@ toml = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
alloy-primitives = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
thiserror = { workspace = true }
Expand All @@ -66,12 +70,12 @@ cowprotocol = { version = "0.2.0", default-features = false }
# `--no-default-features` drops everything so downstream can depend on a
# single slice without pulling the codec or the keeper transitively.
default = ["body"]
body = ["dep:borsh", "dep:videre-sdk"]
body = ["dep:borsh", "dep:videre-sdk", "dep:alloy-primitives"]
client = ["body", "dep:nexum-sdk"]
# Chain-edge order assembly, shared by the adapter's submit and the
# keeper's legacy submit path. Carries no component glue, so a keeper
# module can link it without exporting the adapter face.
assembly = ["body", "dep:cowprotocol", "dep:alloy-primitives", "dep:alloy-sol-types"]
assembly = ["body", "dep:cowprotocol", "dep:alloy-sol-types"]
# The venue-adapter component slice: the `#[videre_sdk::venue]` export
# and the orderbook transport. Only the cdylib wasm build enables it.
adapter = [
Expand Down
67 changes: 16 additions & 51 deletions crates/cow-venue/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
use core::time::Duration;
use std::sync::{PoisonError, RwLock};

use alloy_primitives::{Address, U256};
use alloy_primitives::Address;
use cowprotocol::{
ApiError, Chain, OrderCreation, OrderData, OrderKind, OrderStatus, QuoteAppData, QuoteRequest,
};
use nexum_sdk::keeper::RetryAction;
use serde::Deserialize;
use url::Url;
use videre_sdk::transport::http::Fetch;
use videre_sdk::value_flow::{Asset, AssetAmount, Erc20};
use videre_sdk::value_flow::AssetAmount;
use videre_sdk::{
AuthScheme, IntentBody as _, IntentHeader, IntentStatus, Quotation, RateLimit, Settlement,
SubmitOutcome, UnsignedTx, VenueError,
Expand Down Expand Up @@ -149,8 +149,8 @@ pub(crate) fn derive_header_with(chain: u64, body: &[u8]) -> Result<IntentHeader
CowIntent::Signed(signed) => (&signed.order, AuthScheme::Eip1271),
};
Ok(IntentHeader {
gives: erc20(order.sell_token, minimal_be(&order.sell_amount)),
wants: erc20(order.buy_token, minimal_be(&order.buy_amount)),
gives: AssetAmount::erc20(order.sell_token, order.sell_amount),
wants: AssetAmount::erc20(order.buy_token, order.buy_amount),
settlement: Settlement { chain },
authorisation,
})
Expand All @@ -170,7 +170,7 @@ pub(crate) fn submit_with(
match decode(body)? {
CowIntent::Signed(signed) => {
let order = assembly::body_to_order_data(&signed.order);
let owner = Address::from(signed.owner);
let owner = signed.owner;
let creation = assembly::build_order_creation(&order, &signed.signature, owner)
.map_err(|e| VenueError::InvalidBody(e.to_string()))?;
let uid = match post_order(fetch, config, &creation)? {
Expand Down Expand Up @@ -243,7 +243,7 @@ pub(crate) fn quote_with(
let intent = decode(body)?;
let (wire, from) = match &intent {
CowIntent::Order(order) => (order, config.owner.ok_or(VenueError::Unsupported)?),
CowIntent::Signed(signed) => (&signed.order, Address::from(signed.owner)),
CowIntent::Signed(signed) => (&signed.order, signed.owner),
};
let order = assembly::body_to_order_data(wire);
let request = serde_json::to_vec(&quote_request(&order, from))
Expand All @@ -260,18 +260,9 @@ pub(crate) fn quote_with(
let quoted: cowprotocol::OrderQuoteResponse = serde_json::from_slice(response.body())
.map_err(|e| VenueError::Unavailable(format!("quote decode failed: {e}")))?;
Ok(Quotation {
gives: erc20(
order.sell_token.into_array(),
minimal_be_u256(quoted.quote.sell_amount),
),
wants: erc20(
order.buy_token.into_array(),
minimal_be_u256(quoted.quote.buy_amount),
),
fee: erc20(
order.sell_token.into_array(),
minimal_be_u256(quoted.quote.fee_amount),
),
gives: AssetAmount::erc20(order.sell_token, quoted.quote.sell_amount),
wants: AssetAmount::erc20(order.buy_token, quoted.quote.buy_amount),
fee: AssetAmount::erc20(order.sell_token, quoted.quote.fee_amount),
valid_until_ms: u64::from(quoted.quote.valid_to).saturating_mul(1000),
})
}
Expand Down Expand Up @@ -435,28 +426,6 @@ fn classified(api: &ApiError) -> VenueError {
}
}

// ── value projections ────────────────────────────────────────────────

/// Big-endian bytes with leading zeros trimmed: the minimal `uint`
/// spelling, where an empty list is zero.
fn minimal_be(bytes: &[u8; 32]) -> Vec<u8> {
let first = bytes.iter().position(|byte| *byte != 0);
first.map_or(Vec::new(), |index| bytes[index..].to_vec())
}

fn minimal_be_u256(value: U256) -> Vec<u8> {
minimal_be(&value.to_be_bytes::<32>())
}

fn erc20(token: [u8; 20], amount: Vec<u8>) -> AssetAmount {
AssetAmount {
asset: Asset::Erc20(Erc20 {
token: token.to_vec(),
}),
amount,
}
}

// The component-ABI export glue only exists on the wasm build; the
// native build keeps the same trait impl (for conformance suites)
// without export symbols no native linker accepts.
Expand Down Expand Up @@ -530,8 +499,10 @@ mod export {

#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use videre_sdk::transport::BoundedFetch;
use videre_sdk::transport::http::FetchError;
use videre_sdk::value_flow::{Asset, Erc20};
use videre_sdk::{IntentBody as _, VenueFault};
use videre_test::MockFetch;
use videre_test::reconcile::ReconcileFixture;
Expand Down Expand Up @@ -566,26 +537,20 @@ mod tests {

fn order_body() -> OrderBody {
OrderBody::sell(
SellToken([0x11; 20]),
amount(42),
BuyToken([0x22; 20]),
amount(41),
SellToken(Address::repeat_byte(0x11)),
U256::from(42u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(41u64),
1_700_000_000,
)
.app_data([0x44; 32])
.build()
}

fn amount(value: u8) -> [u8; 32] {
let mut bytes = [0u8; 32];
bytes[31] = value;
bytes
}

fn signed_bytes() -> Vec<u8> {
CowIntentBody::V1(CowIntent::Signed(SignedOrder {
order: order_body(),
owner: owner().into_array(),
owner: owner(),
signature: vec![0xC0, 0xFF, 0xEE],
}))
.to_bytes()
Expand Down
40 changes: 18 additions & 22 deletions crates/cow-venue/src/assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
//! the adapter, not the keeper, owns every projection across that
//! edge.

use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;

use alloy_primitives::{Address, Bytes};
use alloy_sol_types::SolCall;
use cowprotocol::{
Expand Down Expand Up @@ -113,14 +109,14 @@ pub fn order_uid(chain: Chain, order: &OrderData, owner: Address) -> cowprotocol
#[must_use]
pub fn order_data_to_body(order: &OrderData) -> OrderBody {
OrderBody {
sell_token: order.sell_token.into_array(),
buy_token: order.buy_token.into_array(),
receiver: order.receiver.map(Address::into_array),
sell_amount: order.sell_amount.to_be_bytes(),
buy_amount: order.buy_amount.to_be_bytes(),
sell_token: order.sell_token,
buy_token: order.buy_token,
receiver: order.receiver,
sell_amount: order.sell_amount,
buy_amount: order.buy_amount,
valid_to: order.valid_to,
app_data: order.app_data.0,
fee_amount: order.fee_amount.to_be_bytes(),
fee_amount: order.fee_amount,
kind: match order.kind {
OrderKind::Sell => crate::order::OrderKind::Sell,
OrderKind::Buy => crate::order::OrderKind::Buy,
Expand All @@ -143,14 +139,14 @@ pub fn order_data_to_body(order: &OrderData) -> OrderBody {
#[must_use]
pub fn body_to_order_data(body: &OrderBody) -> OrderData {
OrderData {
sell_token: Address::from(body.sell_token),
buy_token: Address::from(body.buy_token),
receiver: body.receiver.map(Address::from),
sell_amount: alloy_primitives::U256::from_be_bytes(body.sell_amount),
buy_amount: alloy_primitives::U256::from_be_bytes(body.buy_amount),
sell_token: body.sell_token,
buy_token: body.buy_token,
receiver: body.receiver,
sell_amount: body.sell_amount,
buy_amount: body.buy_amount,
valid_to: body.valid_to,
app_data: body.app_data.into(),
fee_amount: alloy_primitives::U256::from_be_bytes(body.fee_amount),
fee_amount: body.fee_amount,
kind: match body.kind {
crate::order::OrderKind::Sell => OrderKind::Sell,
crate::order::OrderKind::Buy => OrderKind::Buy,
Expand Down Expand Up @@ -283,14 +279,14 @@ mod tests {
let g = submittable_gpv2();
let order = gpv2_to_order_data(&g).expect("known markers");
let body = order_data_to_body(&order);
assert_eq!(body.sell_token, g.sellToken.into_array());
assert_eq!(body.buy_token, g.buyToken.into_array());
assert_eq!(body.receiver, Some(g.receiver.into_array()));
assert_eq!(body.sell_amount, g.sellAmount.to_be_bytes::<32>());
assert_eq!(body.buy_amount, g.buyAmount.to_be_bytes::<32>());
assert_eq!(body.sell_token, g.sellToken);
assert_eq!(body.buy_token, g.buyToken);
assert_eq!(body.receiver, Some(g.receiver));
assert_eq!(body.sell_amount, g.sellAmount);
assert_eq!(body.buy_amount, g.buyAmount);
assert_eq!(body.valid_to, g.validTo);
assert_eq!(body.app_data, g.appData.0);
assert_eq!(body.fee_amount, g.feeAmount.to_be_bytes::<32>());
assert_eq!(body.fee_amount, g.feeAmount);
assert_eq!(body.kind, crate::order::OrderKind::Sell);
assert!(!body.partially_fillable);
assert_eq!(
Expand Down
13 changes: 7 additions & 6 deletions crates/cow-venue/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,18 @@ pub enum CowIntentBody {

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{Address, U256};
use videre_test::{CodecVectors, Expectation};

use super::*;
use crate::order::{BuyToken, SellToken};

fn order_body() -> OrderBody {
OrderBody::sell(
SellToken([0x11; 20]),
[0x01; 32],
BuyToken([0x22; 20]),
[0x02; 32],
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1_700_000_000,
)
.app_data([0x44; 32])
Expand All @@ -68,7 +69,7 @@ mod tests {
"v1-signed",
&CowIntentBody::V1(CowIntent::Signed(SignedOrder {
order: order_body(),
owner: [0x55; 20],
owner: Address::repeat_byte(0x55),
signature: vec![0xC0, 0xFF, 0xEE],
})),
)
Expand Down
23 changes: 12 additions & 11 deletions crates/cow-venue/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
//! slice so the client that submits an order and the table that
//! classifies its rejection version together.

use alloc::string::String;

use videre_sdk::client::{HostVenues, Venue, VenueClient};
use videre_sdk::keeper::submission_key;
use videre_sdk::{BodyError, IntentBody as _};
Expand Down Expand Up @@ -96,14 +94,16 @@ mod tests {
}

fn sample_body() -> CowIntentBody {
use alloy_primitives::{Address, U256};

use crate::body::CowIntent;
use crate::order::{BuyToken, OrderBody, SellToken};
CowIntentBody::V1(CowIntent::Order(
OrderBody::sell(
SellToken([0x11; 20]),
[0x01; 32],
BuyToken([0x22; 20]),
[0x02; 32],
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1_700_000_000,
)
.app_data([0x44; 32])
Expand All @@ -114,6 +114,7 @@ mod tests {

#[test]
fn intent_id_is_deterministic_and_body_scoped() {
use alloy_primitives::{Address, U256};
use videre_sdk::IntentBody;

use crate::body::CowIntent;
Expand All @@ -131,14 +132,14 @@ mod tests {

let other = CowIntentBody::V1(CowIntent::Signed(SignedOrder {
order: OrderBody::sell(
SellToken([0x11; 20]),
[0x01; 32],
BuyToken([0x22; 20]),
[0x02; 32],
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1_700_000_000,
)
.build(),
owner: [0x55; 20],
owner: Address::repeat_byte(0x55),
signature: vec![0xC0],
}));
assert_ne!(id, intent_id(&other).expect("body encodes"));
Expand Down
14 changes: 4 additions & 10 deletions crates/cow-venue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
//! own crate and never here.
//!
//! The body slice is dependency-light on purpose. It links only the
//! venue SDK (for the [`IntentBody`](videre_sdk::IntentBody) derive)
//! and borsh, so a venue adapter component or a strategy module can carry
//! the body types and codec without dragging in the host-side CoW
//! machinery. The crate is `#![no_std]` (tests and the `adapter` slice
//! aside): the derive's generated code reaches `alloc` through the
//! venue SDK re-export, never `::std`.
//! venue SDK (for the [`IntentBody`](videre_sdk::IntentBody) derive),
//! borsh, and the alloy primitives the body fields carry, so a venue
//! adapter component or a strategy module can carry the body types and
//! codec without dragging in the host-side CoW machinery.
//!
//! With `--no-default-features` the slice drops out entirely and the
//! crate compiles empty, so a consumer can depend on a single slice
Expand All @@ -34,16 +32,12 @@
//! never linked by a keeper module (linking it would export the
//! adapter face).

#![cfg_attr(not(any(test, feature = "adapter")), no_std)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![warn(missing_docs)]
// wit_bindgen::generate! expands to host-import shims whose arity can
// exceed clippy's too-many-arguments threshold.
#![cfg_attr(feature = "adapter", allow(clippy::too_many_arguments))]

#[cfg(feature = "body")]
extern crate alloc;

#[cfg(feature = "body")]
pub mod body;

Expand Down
Loading
Loading