cow_types/flags.rs
1//! Bitfield encoding and decoding of order and trade flags.
2//!
3//! Mirrors `encodeOrderFlags`, `decodeOrderFlags`, `encodeTradeFlags`,
4//! `decodeTradeFlags`, `encodeSigningScheme`, and `decodeSigningScheme`
5//! from the `TypeScript` `contracts-ts` package.
6
7use cow_errors::CowError;
8
9use crate::{OrderKind, SigningScheme, TokenBalance};
10
11/// Order flags extracted from a bitfield.
12///
13/// Corresponds to the `TypeScript` `OrderFlags` type.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct OrderFlags {
16 /// The order kind (sell or buy).
17 pub kind: OrderKind,
18 /// Whether the order is partially fillable.
19 pub partially_fillable: bool,
20 /// Source of sell token balance.
21 pub sell_token_balance: TokenBalance,
22 /// Destination of buy token balance.
23 pub buy_token_balance: TokenBalance,
24}
25
26/// Trade flags: order flags plus a signing scheme.
27///
28/// Corresponds to the `TypeScript` `TradeFlags` type.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct TradeFlags {
31 /// The underlying order flags.
32 pub order_flags: OrderFlags,
33 /// The signing scheme used to encode the signature.
34 pub signing_scheme: SigningScheme,
35}
36
37// ── Bit layout ──────────────────────────────────────────────────────────────
38//
39// Bit 0: kind (0 = sell, 1 = buy)
40// Bit 1: partiallyFillable (0 = false, 1 = true)
41// Bits 2-3: sellTokenBalance (0 = erc20, 1 = unused, 2 = external, 3 = internal)
42// Bit 4: buyTokenBalance (0 = erc20, 1 = internal)
43// Bits 5-6: signingScheme (0 = eip712, 1 = ethsign, 2 = eip1271, 3 = presign)
44
45const KIND_OFFSET: u8 = 0;
46const PARTIALLY_FILLABLE_OFFSET: u8 = 1;
47const SELL_TOKEN_BALANCE_OFFSET: u8 = 2;
48const BUY_TOKEN_BALANCE_OFFSET: u8 = 4;
49const SIGNING_SCHEME_OFFSET: u8 = 5;
50
51/// Encode an [`OrderKind`] into its flag bits.
52///
53/// # Arguments
54///
55/// * `kind` — the order kind to encode.
56///
57/// # Returns
58///
59/// A `u8` with bit 0 set to `0` for [`OrderKind::Sell`] or `1` for
60/// [`OrderKind::Buy`].
61#[must_use]
62pub const fn encode_kind(kind: OrderKind) -> u8 {
63 match kind {
64 OrderKind::Sell => 0,
65 OrderKind::Buy => 1,
66 }
67}
68
69/// Encode a `partially_fillable` bool into its flag bits.
70///
71/// # Arguments
72///
73/// * `pf` — `true` if the order is partially fillable.
74///
75/// # Returns
76///
77/// A `u8` with bit 1 set according to `pf`.
78#[must_use]
79pub const fn encode_partially_fillable(pf: bool) -> u8 {
80 (pf as u8) << PARTIALLY_FILLABLE_OFFSET
81}
82
83/// Encode a sell [`TokenBalance`] into its flag bits.
84///
85/// # Arguments
86///
87/// * `balance` — the sell token balance source.
88///
89/// # Returns
90///
91/// A `u8` with bits 2-3 encoding the balance type (`Erc20` = 0, `External` = 2,
92/// `Internal` = 3).
93#[must_use]
94pub const fn encode_sell_token_balance(balance: TokenBalance) -> u8 {
95 let index = match balance {
96 TokenBalance::Erc20 => 0u8,
97 TokenBalance::External => 2,
98 TokenBalance::Internal => 3,
99 };
100 index << SELL_TOKEN_BALANCE_OFFSET
101}
102
103/// Encode a buy [`TokenBalance`] into its flag bits.
104///
105/// Note: `External` is normalized to `Erc20` for buy token balance, matching
106/// the `TypeScript` `normalizeBuyTokenBalance` behavior.
107///
108/// # Arguments
109///
110/// * `balance` — the buy token balance destination.
111///
112/// # Returns
113///
114/// A `u8` with bit 4 encoding the balance type (`Erc20`/`External` = 0,
115/// `Internal` = 1).
116#[must_use]
117pub const fn encode_buy_token_balance(balance: TokenBalance) -> u8 {
118 let index = match balance {
119 TokenBalance::Erc20 | TokenBalance::External => 0u8,
120 TokenBalance::Internal => 1,
121 };
122 index << BUY_TOKEN_BALANCE_OFFSET
123}
124
125/// Encode a [`SigningScheme`] into its flag bits.
126///
127/// Mirrors `encodeSigningScheme` from the `TypeScript` SDK.
128///
129/// # Arguments
130///
131/// * `scheme` — the signing scheme to encode.
132///
133/// # Returns
134///
135/// A `u8` with bits 5-6 encoding the scheme (`Eip712` = 0, `EthSign` = 1,
136/// `Eip1271` = 2, `PreSign` = 3).
137///
138/// ```
139/// use cow_types::{SigningScheme, flags::encode_signing_scheme};
140///
141/// assert_eq!(encode_signing_scheme(SigningScheme::Eip712), 0b00_00000);
142/// assert_eq!(encode_signing_scheme(SigningScheme::EthSign), 0b01_00000);
143/// assert_eq!(encode_signing_scheme(SigningScheme::Eip1271), 0b10_00000);
144/// assert_eq!(encode_signing_scheme(SigningScheme::PreSign), 0b11_00000);
145/// ```
146#[must_use]
147pub const fn encode_signing_scheme(scheme: SigningScheme) -> u8 {
148 let index = match scheme {
149 SigningScheme::Eip712 => 0u8,
150 SigningScheme::EthSign => 1,
151 SigningScheme::Eip1271 => 2,
152 SigningScheme::PreSign => 3,
153 };
154 index << SIGNING_SCHEME_OFFSET
155}
156
157/// Encode order flags as a single byte bitfield.
158///
159/// Mirrors `encodeOrderFlags` from the `TypeScript` SDK.
160///
161/// # Arguments
162///
163/// * `flags` — the order flags to encode.
164///
165/// # Returns
166///
167/// A `u8` bitfield combining the kind, partially-fillable, sell-token-balance,
168/// and buy-token-balance flags.
169///
170/// ```no_run
171/// use cow_types::{
172/// OrderKind, TokenBalance,
173/// flags::{OrderFlags, encode_order_flags},
174/// };
175///
176/// let flags = OrderFlags {
177/// kind: OrderKind::Sell,
178/// partially_fillable: false,
179/// sell_token_balance: TokenBalance::Erc20,
180/// buy_token_balance: TokenBalance::Erc20,
181/// };
182/// assert_eq!(encode_order_flags(&flags), 0);
183///
184/// let flags = OrderFlags {
185/// kind: OrderKind::Buy,
186/// partially_fillable: true,
187/// sell_token_balance: TokenBalance::External,
188/// buy_token_balance: TokenBalance::Internal,
189/// };
190/// assert_eq!(encode_order_flags(&flags), 0b1_1011);
191/// ```
192#[must_use]
193pub const fn encode_order_flags(flags: &OrderFlags) -> u8 {
194 encode_kind(flags.kind) |
195 encode_partially_fillable(flags.partially_fillable) |
196 encode_sell_token_balance(flags.sell_token_balance) |
197 encode_buy_token_balance(flags.buy_token_balance)
198}
199
200/// Decode order flags from a bitfield.
201///
202/// Mirrors `decodeOrderFlags` from the `TypeScript` SDK.
203///
204/// # Arguments
205///
206/// * `bits` — the encoded bitfield byte.
207///
208/// # Returns
209///
210/// The decoded [`OrderFlags`] struct.
211///
212/// # Errors
213///
214/// Returns [`CowError::Parse`] if any flag field has an invalid index.
215///
216/// ```no_run
217/// use cow_types::{
218/// OrderKind, TokenBalance,
219/// flags::{OrderFlags, decode_order_flags, encode_order_flags},
220/// };
221///
222/// let flags = OrderFlags {
223/// kind: OrderKind::Buy,
224/// partially_fillable: true,
225/// sell_token_balance: TokenBalance::Internal,
226/// buy_token_balance: TokenBalance::Internal,
227/// };
228/// let encoded = encode_order_flags(&flags);
229/// let decoded = decode_order_flags(encoded).unwrap();
230/// assert_eq!(decoded, flags);
231/// ```
232pub fn decode_order_flags(bits: u8) -> Result<OrderFlags, CowError> {
233 let kind_index = (bits >> KIND_OFFSET) & 0x01;
234 let pf_index = (bits >> PARTIALLY_FILLABLE_OFFSET) & 0x01;
235 let sell_index = (bits >> SELL_TOKEN_BALANCE_OFFSET) & 0x03;
236 let buy_index = (bits >> BUY_TOKEN_BALANCE_OFFSET) & 0x01;
237
238 let kind = match kind_index {
239 0 => OrderKind::Sell,
240 1 => OrderKind::Buy,
241 _ => unreachable!(),
242 };
243
244 let partially_fillable = pf_index != 0;
245
246 let sell_token_balance = match sell_index {
247 0 => TokenBalance::Erc20,
248 2 => TokenBalance::External,
249 3 => TokenBalance::Internal,
250 other => {
251 return Err(CowError::Parse {
252 field: "sellTokenBalance",
253 reason: format!("invalid flag index: {other}"),
254 });
255 }
256 };
257
258 let buy_token_balance = match buy_index {
259 0 => TokenBalance::Erc20,
260 1 => TokenBalance::Internal,
261 _ => unreachable!(),
262 };
263
264 Ok(OrderFlags { kind, partially_fillable, sell_token_balance, buy_token_balance })
265}
266
267/// Decode a [`SigningScheme`] from a trade-flags bitfield.
268///
269/// Mirrors `decodeSigningScheme` from the `TypeScript` SDK.
270///
271/// # Arguments
272///
273/// * `bits` — the encoded trade-flags bitfield byte.
274///
275/// # Returns
276///
277/// The decoded [`SigningScheme`] variant.
278///
279/// # Errors
280///
281/// Returns [`CowError::Parse`] if the signing scheme index is invalid.
282///
283/// ```no_run
284/// use cow_types::{
285/// SigningScheme,
286/// flags::{decode_signing_scheme, encode_signing_scheme},
287/// };
288///
289/// let bits = encode_signing_scheme(SigningScheme::Eip1271);
290/// assert_eq!(decode_signing_scheme(bits).unwrap(), SigningScheme::Eip1271);
291/// ```
292pub fn decode_signing_scheme(bits: u8) -> Result<SigningScheme, CowError> {
293 let index = (bits >> SIGNING_SCHEME_OFFSET) & 0x03;
294 match index {
295 0 => Ok(SigningScheme::Eip712),
296 1 => Ok(SigningScheme::EthSign),
297 2 => Ok(SigningScheme::Eip1271),
298 3 => Ok(SigningScheme::PreSign),
299 _ => unreachable!(),
300 }
301}
302
303/// Encode trade flags (order flags + signing scheme) as a single byte bitfield.
304///
305/// Mirrors `encodeTradeFlags` from the `TypeScript` SDK.
306///
307/// # Arguments
308///
309/// * `flags` — the trade flags to encode.
310///
311/// # Returns
312///
313/// A `u8` bitfield combining order flags (bits 0-4) and signing scheme
314/// (bits 5-6).
315///
316/// ```no_run
317/// use cow_types::{
318/// OrderKind, SigningScheme, TokenBalance,
319/// flags::{OrderFlags, TradeFlags, encode_trade_flags},
320/// };
321///
322/// let flags = TradeFlags {
323/// order_flags: OrderFlags {
324/// kind: OrderKind::Sell,
325/// partially_fillable: false,
326/// sell_token_balance: TokenBalance::Erc20,
327/// buy_token_balance: TokenBalance::Erc20,
328/// },
329/// signing_scheme: SigningScheme::Eip712,
330/// };
331/// assert_eq!(encode_trade_flags(&flags), 0);
332/// ```
333#[must_use]
334pub const fn encode_trade_flags(flags: &TradeFlags) -> u8 {
335 encode_order_flags(&flags.order_flags) | encode_signing_scheme(flags.signing_scheme)
336}
337
338/// Decode trade flags from a bitfield.
339///
340/// Mirrors `decodeTradeFlags` from the `TypeScript` SDK.
341///
342/// # Arguments
343///
344/// * `bits` — the encoded trade-flags bitfield byte.
345///
346/// # Returns
347///
348/// The decoded [`TradeFlags`] struct containing order flags and signing scheme.
349///
350/// # Errors
351///
352/// Returns [`CowError::Parse`] if any flag field has an invalid index.
353///
354/// ```no_run
355/// use cow_types::{
356/// OrderKind, SigningScheme, TokenBalance,
357/// flags::{OrderFlags, TradeFlags, decode_trade_flags, encode_trade_flags},
358/// };
359///
360/// let flags = TradeFlags {
361/// order_flags: OrderFlags {
362/// kind: OrderKind::Buy,
363/// partially_fillable: true,
364/// sell_token_balance: TokenBalance::External,
365/// buy_token_balance: TokenBalance::Internal,
366/// },
367/// signing_scheme: SigningScheme::PreSign,
368/// };
369/// let encoded = encode_trade_flags(&flags);
370/// let decoded = decode_trade_flags(encoded).unwrap();
371/// assert_eq!(decoded, flags);
372/// ```
373pub fn decode_trade_flags(bits: u8) -> Result<TradeFlags, CowError> {
374 let order_flags = decode_order_flags(bits)?;
375 let signing_scheme = decode_signing_scheme(bits)?;
376 Ok(TradeFlags { order_flags, signing_scheme })
377}
378
379/// Normalize the buy token balance, converting `External` to `Erc20`.
380///
381/// In the `CoW` Protocol, the `External` balance type only applies to sell
382/// tokens. For buy tokens, `External` is treated as `Erc20`.
383///
384/// Mirrors `normalizeBuyTokenBalance` from the `TypeScript` SDK.
385///
386/// # Arguments
387///
388/// * `balance` — the buy token balance to normalize.
389///
390/// # Returns
391///
392/// The normalized [`TokenBalance`], with [`TokenBalance::External`] mapped to
393/// [`TokenBalance::Erc20`].
394///
395/// ```
396/// use cow_types::{TokenBalance, flags::normalize_buy_token_balance};
397///
398/// assert_eq!(normalize_buy_token_balance(TokenBalance::Erc20), TokenBalance::Erc20);
399/// assert_eq!(normalize_buy_token_balance(TokenBalance::External), TokenBalance::Erc20);
400/// assert_eq!(normalize_buy_token_balance(TokenBalance::Internal), TokenBalance::Internal);
401/// ```
402#[must_use]
403pub const fn normalize_buy_token_balance(balance: TokenBalance) -> TokenBalance {
404 match balance {
405 TokenBalance::Erc20 | TokenBalance::External => TokenBalance::Erc20,
406 TokenBalance::Internal => TokenBalance::Internal,
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn roundtrip_order_flags() {
416 let cases = [
417 OrderFlags {
418 kind: OrderKind::Sell,
419 partially_fillable: false,
420 sell_token_balance: TokenBalance::Erc20,
421 buy_token_balance: TokenBalance::Erc20,
422 },
423 OrderFlags {
424 kind: OrderKind::Buy,
425 partially_fillable: true,
426 sell_token_balance: TokenBalance::External,
427 buy_token_balance: TokenBalance::Internal,
428 },
429 OrderFlags {
430 kind: OrderKind::Sell,
431 partially_fillable: true,
432 sell_token_balance: TokenBalance::Internal,
433 buy_token_balance: TokenBalance::Erc20,
434 },
435 ];
436
437 for flags in &cases {
438 let encoded = encode_order_flags(flags);
439 let decoded = decode_order_flags(encoded).unwrap();
440 assert_eq!(&decoded, flags, "roundtrip failed for {flags:?}");
441 }
442 }
443
444 #[test]
445 fn roundtrip_trade_flags() {
446 let flags = TradeFlags {
447 order_flags: OrderFlags {
448 kind: OrderKind::Buy,
449 partially_fillable: false,
450 sell_token_balance: TokenBalance::Erc20,
451 buy_token_balance: TokenBalance::Internal,
452 },
453 signing_scheme: SigningScheme::Eip1271,
454 };
455 let encoded = encode_trade_flags(&flags);
456 let decoded = decode_trade_flags(encoded).unwrap();
457 assert_eq!(decoded, flags);
458 }
459
460 #[test]
461 fn invalid_sell_token_balance_flag() {
462 // Bit pattern 01 at offset 2 is unused
463 let bits = 0b000_0100;
464 let result = decode_order_flags(bits);
465 assert!(result.is_err());
466 }
467
468 #[test]
469 fn signing_scheme_round_trip() {
470 for scheme in [
471 SigningScheme::Eip712,
472 SigningScheme::EthSign,
473 SigningScheme::Eip1271,
474 SigningScheme::PreSign,
475 ] {
476 let encoded = encode_signing_scheme(scheme);
477 let decoded = decode_signing_scheme(encoded).unwrap();
478 assert_eq!(decoded, scheme);
479 }
480 }
481}