1use std::fmt;
4
5use alloy_primitives::{Address, B256, U256, keccak256};
6use cow_errors::CowError;
7use cow_types::OrderKind;
8
9use super::types::{
10 ConditionalOrderParams, DurationOfPart, PollResult, TWAP_HANDLER_ADDRESS, TwapData,
11 TwapStartTime, TwapStruct,
12};
13
14#[derive(Debug, Clone)]
16pub struct TwapOrder {
17 pub data: TwapData,
19 pub salt: B256,
21}
22
23impl TwapOrder {
24 #[must_use]
41 pub fn new(data: TwapData) -> Self {
42 let salt = deterministic_salt(&data);
43 Self { data, salt }
44 }
45
46 #[must_use]
57 pub const fn with_salt(data: TwapData, salt: B256) -> Self {
58 Self { data, salt }
59 }
60
61 pub fn to_params(&self) -> Result<ConditionalOrderParams, CowError> {
67 Ok(ConditionalOrderParams {
68 handler: TWAP_HANDLER_ADDRESS,
69 salt: self.salt,
70 static_input: encode_twap_static_input(&self.data)?,
71 })
72 }
73
74 pub fn id(&self) -> Result<B256, CowError> {
80 Ok(order_id(&self.to_params()?))
81 }
82
83 #[must_use]
93 pub fn is_valid(&self) -> bool {
94 use super::types::MAX_FREQUENCY;
95 let d = &self.data;
96 if d.sell_token == d.buy_token {
97 return false;
98 }
99 if d.sell_token.is_zero() || d.buy_token.is_zero() {
100 return false;
101 }
102 if d.sell_amount.is_zero() || d.buy_amount.is_zero() {
103 return false;
104 }
105 if d.num_parts < 2 {
106 return false;
107 }
108 if d.part_duration == 0 || d.part_duration > MAX_FREQUENCY {
109 return false;
110 }
111 if !(d.sell_amount % U256::from(d.num_parts)).is_zero() {
112 return false;
113 }
114 if let DurationOfPart::LimitDuration { duration } = d.duration_of_part &&
115 duration > d.part_duration
116 {
117 return false;
118 }
119 true
120 }
121
122 #[allow(clippy::type_complexity, reason = "two-element tuple is readable as-is")]
145 pub fn per_part_amounts(&self) -> Result<(U256, U256), CowError> {
146 let n = self.data.num_parts;
147 if n == 0 {
148 return Err(CowError::AppData("num_parts must be > 0".into()));
149 }
150 let divisor = U256::from(n);
151 Ok((self.data.sell_amount / divisor, self.data.buy_amount / divisor))
152 }
153
154 pub fn to_struct(&self) -> Result<TwapStruct, CowError> {
162 data_to_struct(&self.data)
163 }
164
165 #[must_use]
174 pub fn poll_validate(&self, block_timestamp: u64) -> PollResult {
175 let d = &self.data;
176 let start = match d.start_time {
177 TwapStartTime::AtMiningTime => {
178 return PollResult::Success { order: None, signature: None };
179 }
180 TwapStartTime::At(ts) => u64::from(ts),
181 };
182 let end = start + u64::from(d.num_parts) * u64::from(d.part_duration);
183
184 if block_timestamp < start {
185 return PollResult::TryAtEpoch { epoch: start };
186 }
187 if block_timestamp >= end {
188 return PollResult::DontTryAgain { reason: "TWAP order has fully expired".into() };
189 }
190 PollResult::Success { order: None, signature: None }
191 }
192}
193
194impl TwapOrder {
195 #[must_use]
202 pub const fn salt_ref(&self) -> &B256 {
203 &self.salt
204 }
205
206 #[must_use]
212 pub const fn data_ref(&self) -> &TwapData {
213 &self.data
214 }
215
216 #[must_use]
227 pub const fn total_sell_amount(&self) -> U256 {
228 self.data.sell_amount
229 }
230
231 #[must_use]
243 pub const fn total_buy_amount(&self) -> U256 {
244 self.data.buy_amount
245 }
246
247 #[must_use]
253 pub const fn is_sell(&self) -> bool {
254 self.data.is_sell()
255 }
256
257 #[must_use]
263 pub const fn is_buy(&self) -> bool {
264 self.data.is_buy()
265 }
266
267 #[must_use]
284 pub const fn is_expired_at(&self, block_timestamp: u64) -> bool {
285 self.data.is_expired(block_timestamp)
286 }
287
288 #[must_use]
292 pub const fn start_timestamp(&self) -> Option<u32> {
293 self.data.start_time.timestamp()
294 }
295}
296
297impl fmt::Display for TwapOrder {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 fmt::Display::fmt(&self.data, f)
300 }
301}
302
303#[must_use]
326pub fn order_id(params: &ConditionalOrderParams) -> B256 {
327 let mut buf = Vec::with_capacity(4 * 32 + pad32_len(params.static_input.len()));
328 buf.extend_from_slice(&pad_address(params.handler.as_slice()));
329 buf.extend_from_slice(params.salt.as_slice());
330 buf.extend_from_slice(&u256_be(96u64));
331 buf.extend_from_slice(&u256_be(params.static_input.len() as u64));
332 pad_into(&mut buf, ¶ms.static_input);
333 keccak256(&buf)
334}
335
336#[must_use]
362pub fn encode_params(params: &ConditionalOrderParams) -> String {
363 let static_len = params.static_input.len();
364 let padded_len = pad32_len(static_len);
365 let mut buf = Vec::with_capacity(4 * 32 + padded_len);
366 buf.extend_from_slice(&pad_address(params.handler.as_slice()));
367 buf.extend_from_slice(params.salt.as_slice());
368 buf.extend_from_slice(&u256_be(96u64)); buf.extend_from_slice(&u256_be(static_len as u64));
370 pad_into(&mut buf, ¶ms.static_input);
371 format!("0x{}", alloy_primitives::hex::encode(&buf))
372}
373
374pub fn decode_params(hex: &str) -> Result<ConditionalOrderParams, CowError> {
401 let stripped = hex.trim_start_matches("0x");
402 let bytes = alloy_primitives::hex::decode(stripped)
403 .map_err(|e| CowError::AppData(format!("decode_params hex: {e}")))?;
404 if bytes.len() < 4 * 32 {
405 return Err(CowError::AppData(format!(
406 "decode_params: too short ({} bytes, need ≥ 128)",
407 bytes.len()
408 )));
409 }
410 let mut handler_bytes = [0u8; 20];
411 handler_bytes.copy_from_slice(&bytes[12..32]);
412 let handler = Address::new(handler_bytes);
413
414 let mut salt_bytes = [0u8; 32];
415 salt_bytes.copy_from_slice(&bytes[32..64]);
416 let salt = B256::new(salt_bytes);
417
418 let data_len = usize::try_from(U256::from_be_slice(&bytes[96..128]))
420 .map_err(|_e| CowError::AppData("decode_params: static_input length overflow".into()))?;
421 let data_end = 128usize
422 .checked_add(data_len)
423 .ok_or_else(|| CowError::AppData("decode_params: static_input length overflow".into()))?;
424 if bytes.len() < data_end {
425 return Err(CowError::AppData(format!(
426 "decode_params: data truncated (need {data_end} bytes, have {})",
427 bytes.len()
428 )));
429 }
430 let static_input = bytes[128..data_end].to_vec();
431 Ok(ConditionalOrderParams { handler, salt, static_input })
432}
433
434#[must_use]
447pub fn format_epoch(epoch: u32) -> String {
448 use chrono::{DateTime, Utc};
449 DateTime::<Utc>::from_timestamp(i64::from(epoch), 0)
450 .map_or_else(|| format!("{epoch}"), |dt| dt.to_rfc3339())
451}
452
453fn encode_twap_static_input(d: &TwapData) -> Result<Vec<u8>, CowError> {
478 let s = data_to_struct(d)?;
479 Ok(encode_struct(&s))
480}
481
482pub fn data_to_struct(d: &TwapData) -> Result<TwapStruct, CowError> {
509 if d.num_parts == 0 {
510 return Err(CowError::AppData("num_parts must be > 0".into()));
511 }
512 let n = U256::from(d.num_parts);
513 Ok(TwapStruct {
514 sell_token: d.sell_token,
515 buy_token: d.buy_token,
516 receiver: d.receiver,
517 part_sell_amount: d.sell_amount / n,
518 min_part_limit: d.buy_amount / n,
519 t0: match d.start_time {
520 TwapStartTime::AtMiningTime => 0,
521 TwapStartTime::At(ts) => ts,
522 },
523 n: d.num_parts,
524 t: d.part_duration,
525 span: match d.duration_of_part {
526 DurationOfPart::Auto => 0,
527 DurationOfPart::LimitDuration { duration } => duration,
528 },
529 app_data: d.app_data,
530 })
531}
532
533#[must_use]
566pub fn struct_to_data(s: &TwapStruct) -> TwapData {
567 TwapData {
568 sell_token: s.sell_token,
569 buy_token: s.buy_token,
570 receiver: s.receiver,
571 sell_amount: s.part_sell_amount * U256::from(s.n),
572 buy_amount: s.min_part_limit * U256::from(s.n),
573 start_time: if s.t0 == 0 { TwapStartTime::AtMiningTime } else { TwapStartTime::At(s.t0) },
574 part_duration: s.t,
575 num_parts: s.n,
576 app_data: s.app_data,
577 partially_fillable: false,
578 kind: OrderKind::Sell,
579 duration_of_part: if s.span == 0 {
580 DurationOfPart::Auto
581 } else {
582 DurationOfPart::LimitDuration { duration: s.span }
583 },
584 }
585}
586
587fn encode_struct(s: &TwapStruct) -> Vec<u8> {
597 encode_twap_struct(s)
598}
599
600#[must_use]
628pub fn encode_twap_struct(s: &TwapStruct) -> Vec<u8> {
629 let mut buf = Vec::with_capacity(10 * 32);
630 buf.extend_from_slice(&pad_address(s.sell_token.as_slice()));
631 buf.extend_from_slice(&pad_address(s.buy_token.as_slice()));
632 buf.extend_from_slice(&pad_address(s.receiver.as_slice()));
633 buf.extend_from_slice(&u256_bytes(s.part_sell_amount));
634 buf.extend_from_slice(&u256_bytes(s.min_part_limit));
635 buf.extend_from_slice(&u256_be(u64::from(s.t0)));
636 buf.extend_from_slice(&u256_be(u64::from(s.n)));
637 buf.extend_from_slice(&u256_be(u64::from(s.t)));
638 buf.extend_from_slice(&u256_be(u64::from(s.span)));
639 buf.extend_from_slice(s.app_data.as_slice());
640 buf
641}
642
643pub fn decode_twap_static_input(bytes: &[u8]) -> Result<TwapData, CowError> {
653 Ok(struct_to_data(&decode_twap_struct(bytes)?))
654}
655
656pub fn decode_twap_struct(bytes: &[u8]) -> Result<TwapStruct, CowError> {
685 if bytes.len() < 10 * 32 {
686 return Err(CowError::AppData(format!(
687 "TWAP static input too short: {} bytes (need 320)",
688 bytes.len()
689 )));
690 }
691 let addr = |off: usize| -> Address {
692 let mut a = [0u8; 20];
693 a.copy_from_slice(&bytes[off + 12..off + 32]);
694 Address::new(a)
695 };
696 let u256 = |off: usize| -> U256 { U256::from_be_slice(&bytes[off..off + 32]) };
697 let u32v = |off: usize| -> u32 {
698 u32::from_be_bytes([bytes[off + 28], bytes[off + 29], bytes[off + 30], bytes[off + 31]])
699 };
700
701 let mut app_data = [0u8; 32];
702 app_data.copy_from_slice(&bytes[288..320]);
703
704 Ok(TwapStruct {
705 sell_token: addr(0),
706 buy_token: addr(32),
707 receiver: addr(64),
708 part_sell_amount: u256(96),
709 min_part_limit: u256(128),
710 t0: u32v(160),
711 n: u32v(192),
712 t: u32v(224),
713 span: u32v(256),
714 app_data: B256::new(app_data),
715 })
716}
717
718fn pad_address(bytes: &[u8]) -> [u8; 32] {
730 let mut out = [0u8; 32];
731 out[12..].copy_from_slice(bytes);
732 out
733}
734
735const fn u256_bytes(v: U256) -> [u8; 32] {
745 v.to_be_bytes()
746}
747
748fn u256_be(v: u64) -> [u8; 32] {
759 let mut out = [0u8; 32];
760 out[24..].copy_from_slice(&v.to_be_bytes());
761 out
762}
763
764const fn pad32_len(n: usize) -> usize {
774 if n.is_multiple_of(32) { n } else { n + (32 - n % 32) }
775}
776
777fn pad_into(buf: &mut Vec<u8>, data: &[u8]) {
789 buf.extend_from_slice(data);
790 let rem = data.len() % 32;
791 if rem != 0 {
792 buf.resize(buf.len() + (32 - rem), 0);
793 }
794}
795
796fn deterministic_salt(d: &TwapData) -> B256 {
807 let mut buf = Vec::with_capacity(20 + 20 + 32 + 4);
808 buf.extend_from_slice(d.sell_token.as_slice());
809 buf.extend_from_slice(d.buy_token.as_slice());
810 buf.extend_from_slice(&u256_bytes(d.sell_amount));
811 buf.extend_from_slice(&d.num_parts.to_be_bytes());
812 keccak256(&buf)
813}
814
815#[cfg(test)]
818mod tests {
819 use super::*;
820
821 fn sell_token() -> Address {
822 Address::repeat_byte(0x11)
823 }
824
825 fn buy_token() -> Address {
826 Address::repeat_byte(0x22)
827 }
828
829 fn sample_data() -> TwapData {
830 TwapData::sell(sell_token(), buy_token(), U256::from(1000u64), 4, 3600)
831 .with_buy_amount(U256::from(800u64))
832 }
833
834 #[test]
837 fn encode_decode_twap_struct_roundtrip() {
838 let data = sample_data();
839 let s = data_to_struct(&data).unwrap();
840 let bytes = encode_twap_struct(&s);
841 assert_eq!(bytes.len(), 320);
842 let decoded = decode_twap_struct(&bytes).unwrap();
843 assert_eq!(decoded.sell_token, s.sell_token);
844 assert_eq!(decoded.buy_token, s.buy_token);
845 assert_eq!(decoded.receiver, s.receiver);
846 assert_eq!(decoded.part_sell_amount, s.part_sell_amount);
847 assert_eq!(decoded.min_part_limit, s.min_part_limit);
848 assert_eq!(decoded.t0, s.t0);
849 assert_eq!(decoded.n, s.n);
850 assert_eq!(decoded.t, s.t);
851 assert_eq!(decoded.span, s.span);
852 assert_eq!(decoded.app_data, s.app_data);
853 }
854
855 #[test]
856 fn data_to_struct_to_data_roundtrip() {
857 let data = sample_data();
858 let s = data_to_struct(&data).unwrap();
859 let back = struct_to_data(&s);
860 assert_eq!(back.sell_token, data.sell_token);
861 assert_eq!(back.buy_token, data.buy_token);
862 assert_eq!(back.sell_amount, data.sell_amount);
863 assert_eq!(back.buy_amount, data.buy_amount);
864 assert_eq!(back.num_parts, data.num_parts);
865 assert_eq!(back.part_duration, data.part_duration);
866 }
867
868 #[test]
869 fn decode_twap_static_input_roundtrip() {
870 let data = sample_data();
871 let s = data_to_struct(&data).unwrap();
872 let bytes = encode_twap_struct(&s);
873 let decoded_data = decode_twap_static_input(&bytes).unwrap();
874 assert_eq!(decoded_data.sell_amount, data.sell_amount);
875 assert_eq!(decoded_data.buy_amount, data.buy_amount);
876 }
877
878 #[test]
881 fn decode_twap_struct_too_short() {
882 let result = decode_twap_struct(&[0u8; 319]);
883 assert!(result.is_err());
884 }
885
886 #[test]
887 fn data_to_struct_zero_num_parts() {
888 let mut data = sample_data();
889 data.num_parts = 0;
890 assert!(data_to_struct(&data).is_err());
891 }
892
893 #[test]
896 fn encode_decode_params_roundtrip() {
897 let params = ConditionalOrderParams {
898 handler: Address::repeat_byte(0xaa),
899 salt: B256::new([0xbb; 32]),
900 static_input: vec![0xcc; 50],
901 };
902 let hex = encode_params(¶ms);
903 let decoded = decode_params(&hex).unwrap();
904 assert_eq!(decoded.handler, params.handler);
905 assert_eq!(decoded.salt, params.salt);
906 assert_eq!(decoded.static_input, params.static_input);
907 }
908
909 #[test]
910 fn decode_params_invalid_hex() {
911 assert!(decode_params("0xZZZZ").is_err());
912 }
913
914 #[test]
915 fn decode_params_too_short() {
916 assert!(decode_params("0xabcd").is_err());
917 }
918
919 #[test]
922 fn order_id_deterministic() {
923 let params = ConditionalOrderParams {
924 handler: Address::ZERO,
925 salt: B256::ZERO,
926 static_input: vec![0xab, 0xcd],
927 };
928 let id1 = order_id(¶ms);
929 let id2 = order_id(¶ms);
930 assert_eq!(id1, id2);
931 assert_ne!(id1, B256::ZERO);
932 }
933
934 #[test]
935 fn order_id_changes_with_salt() {
936 let p1 = ConditionalOrderParams {
937 handler: Address::ZERO,
938 salt: B256::ZERO,
939 static_input: vec![],
940 };
941 let p2 = ConditionalOrderParams {
942 handler: Address::ZERO,
943 salt: B256::new([1u8; 32]),
944 static_input: vec![],
945 };
946 assert_ne!(order_id(&p1), order_id(&p2));
947 }
948
949 #[test]
952 fn twap_order_new_deterministic_salt() {
953 let data = sample_data();
954 let order1 = TwapOrder::new(data.clone());
955 let order2 = TwapOrder::new(data);
956 assert_eq!(order1.salt, order2.salt);
957 }
958
959 #[test]
960 fn twap_order_with_salt() {
961 let data = sample_data();
962 let salt = B256::new([0xff; 32]);
963 let order = TwapOrder::with_salt(data, salt);
964 assert_eq!(order.salt, salt);
965 }
966
967 #[test]
968 fn twap_order_to_params_and_id() {
969 let order = TwapOrder::new(sample_data());
970 let params = order.to_params().unwrap();
971 assert_eq!(params.handler, TWAP_HANDLER_ADDRESS);
972 let id = order.id().unwrap();
973 assert_ne!(id, B256::ZERO);
974 }
975
976 #[test]
977 fn twap_order_per_part_amounts() {
978 let data = sample_data();
979 let order = TwapOrder::new(data);
980 let (sell, buy) = order.per_part_amounts().unwrap();
981 assert_eq!(sell, U256::from(250u64));
982 assert_eq!(buy, U256::from(200u64));
983 }
984
985 #[test]
986 fn twap_order_per_part_amounts_zero_parts() {
987 let mut data = sample_data();
988 data.num_parts = 0;
989 let order = TwapOrder::new(data);
990 assert!(order.per_part_amounts().is_err());
991 }
992
993 #[test]
994 fn twap_order_is_valid_happy_path() {
995 let order = TwapOrder::new(sample_data());
996 assert!(order.is_valid());
997 }
998
999 #[test]
1000 fn twap_order_is_valid_same_tokens() {
1001 let mut data = sample_data();
1002 data.buy_token = data.sell_token;
1003 let order = TwapOrder::new(data);
1004 assert!(!order.is_valid());
1005 }
1006
1007 #[test]
1008 fn twap_order_is_valid_zero_sell_token() {
1009 let mut data = sample_data();
1010 data.sell_token = Address::ZERO;
1011 assert!(!TwapOrder::new(data).is_valid());
1012 }
1013
1014 #[test]
1015 fn twap_order_is_valid_zero_sell_amount() {
1016 let mut data = sample_data();
1017 data.sell_amount = U256::ZERO;
1018 assert!(!TwapOrder::new(data).is_valid());
1019 }
1020
1021 #[test]
1022 fn twap_order_is_valid_one_part() {
1023 let mut data = sample_data();
1024 data.num_parts = 1;
1025 assert!(!TwapOrder::new(data).is_valid());
1026 }
1027
1028 #[test]
1029 fn twap_order_is_valid_zero_duration() {
1030 let mut data = sample_data();
1031 data.part_duration = 0;
1032 assert!(!TwapOrder::new(data).is_valid());
1033 }
1034
1035 #[test]
1036 fn twap_order_is_valid_sell_amount_not_divisible() {
1037 let mut data = sample_data();
1038 data.sell_amount = U256::from(1001u64); assert!(!TwapOrder::new(data).is_valid());
1040 }
1041
1042 #[test]
1043 fn twap_order_is_valid_limit_duration_exceeds_part_duration() {
1044 let mut data = sample_data();
1045 data.duration_of_part = DurationOfPart::LimitDuration { duration: 7200 }; assert!(!TwapOrder::new(data).is_valid());
1047 }
1048
1049 #[test]
1050 fn twap_order_is_valid_limit_duration_within_bounds() {
1051 let mut data = sample_data();
1052 data.duration_of_part = DurationOfPart::LimitDuration { duration: 1800 };
1053 assert!(TwapOrder::new(data).is_valid());
1054 }
1055
1056 #[test]
1059 #[allow(clippy::wildcard_enum_match_arm, reason = "test catch-all for unexpected variants")]
1060 fn poll_validate_at_mining_time_always_success() {
1061 let order = TwapOrder::new(sample_data());
1062 match order.poll_validate(0) {
1063 PollResult::Success { .. } => {}
1064 other => panic!("expected Success, got {other:?}"),
1065 }
1066 }
1067
1068 #[test]
1069 #[allow(clippy::wildcard_enum_match_arm, reason = "test catch-all for unexpected variants")]
1070 fn poll_validate_before_start() {
1071 let mut data = sample_data();
1072 data.start_time = TwapStartTime::At(1_000_000);
1073 let order = TwapOrder::new(data);
1074 match order.poll_validate(999_999) {
1075 PollResult::TryAtEpoch { epoch } => assert_eq!(epoch, 1_000_000),
1076 other => panic!("expected TryAtEpoch, got {other:?}"),
1077 }
1078 }
1079
1080 #[test]
1081 #[allow(clippy::wildcard_enum_match_arm, reason = "test catch-all for unexpected variants")]
1082 fn poll_validate_within_window() {
1083 let mut data = sample_data();
1084 data.start_time = TwapStartTime::At(1_000_000);
1085 let order = TwapOrder::new(data);
1086 match order.poll_validate(1_007_000) {
1088 PollResult::Success { .. } => {}
1089 other => panic!("expected Success, got {other:?}"),
1090 }
1091 }
1092
1093 #[test]
1094 #[allow(clippy::wildcard_enum_match_arm, reason = "test catch-all for unexpected variants")]
1095 fn poll_validate_after_expiry() {
1096 let mut data = sample_data();
1097 data.start_time = TwapStartTime::At(1_000_000);
1098 let order = TwapOrder::new(data);
1099 match order.poll_validate(1_014_400) {
1100 PollResult::DontTryAgain { .. } => {}
1101 other => panic!("expected DontTryAgain, got {other:?}"),
1102 }
1103 }
1104
1105 #[test]
1108 fn twap_order_accessors() {
1109 let mut data = sample_data();
1110 data.start_time = TwapStartTime::At(42);
1111 let order = TwapOrder::new(data);
1112 assert_eq!(order.total_sell_amount(), U256::from(1000u64));
1113 assert_eq!(order.total_buy_amount(), U256::from(800u64));
1114 assert!(order.is_sell());
1115 assert!(!order.is_buy());
1116 assert_eq!(order.start_timestamp(), Some(42));
1117 assert_eq!(order.salt_ref(), &order.salt);
1118 assert_eq!(order.data_ref().num_parts, 4);
1119 }
1120
1121 #[test]
1122 fn twap_order_is_expired_at() {
1123 let mut data = sample_data();
1124 data.start_time = TwapStartTime::At(1_000_000);
1125 let order = TwapOrder::new(data);
1126 assert!(!order.is_expired_at(1_014_399));
1127 assert!(order.is_expired_at(1_014_400));
1128 }
1129
1130 #[test]
1131 fn twap_order_at_mining_time_start_timestamp_none() {
1132 let order = TwapOrder::new(sample_data());
1133 assert_eq!(order.start_timestamp(), None);
1134 }
1135
1136 #[test]
1139 fn format_epoch_known_timestamp() {
1140 let s = format_epoch(1_700_000_000);
1141 assert!(s.starts_with("2023-11-14"));
1142 }
1143
1144 #[test]
1147 fn to_struct_with_limit_duration() {
1148 let mut data = sample_data();
1149 data.duration_of_part = DurationOfPart::LimitDuration { duration: 1800 };
1150 let order = TwapOrder::new(data);
1151 let s = order.to_struct().unwrap();
1152 assert_eq!(s.span, 1800);
1153 }
1154
1155 #[test]
1156 fn to_struct_auto_duration() {
1157 let order = TwapOrder::new(sample_data());
1158 let s = order.to_struct().unwrap();
1159 assert_eq!(s.span, 0);
1160 }
1161
1162 #[test]
1165 fn struct_to_data_at_mining_time() {
1166 let s = data_to_struct(&sample_data()).unwrap();
1167 let data = struct_to_data(&s);
1168 assert!(matches!(data.start_time, TwapStartTime::AtMiningTime));
1169 assert!(matches!(data.duration_of_part, DurationOfPart::Auto));
1170 }
1171
1172 #[test]
1173 fn struct_to_data_with_fixed_start() {
1174 let mut d = sample_data();
1175 d.start_time = TwapStartTime::At(12345);
1176 d.duration_of_part = DurationOfPart::LimitDuration { duration: 600 };
1177 let s = data_to_struct(&d).unwrap();
1178 let back = struct_to_data(&s);
1179 assert!(matches!(back.start_time, TwapStartTime::At(12345)));
1180 assert!(matches!(back.duration_of_part, DurationOfPart::LimitDuration { duration: 600 }));
1181 }
1182
1183 #[test]
1186 fn twap_order_display_does_not_panic() {
1187 let order = TwapOrder::new(sample_data());
1188 let s = format!("{order}");
1189 assert!(!s.is_empty());
1190 }
1191
1192 #[test]
1193 fn twap_order_is_valid_zero_buy_token() {
1194 let mut data = sample_data();
1195 data.buy_token = Address::ZERO;
1196 assert!(!TwapOrder::new(data).is_valid());
1197 }
1198
1199 #[test]
1200 fn twap_order_is_valid_zero_buy_amount() {
1201 let mut data = sample_data();
1202 data.buy_amount = U256::ZERO;
1203 assert!(!TwapOrder::new(data).is_valid());
1204 }
1205
1206 #[test]
1207 fn twap_order_is_valid_duration_exceeds_max_frequency() {
1208 let mut data = sample_data();
1209 data.part_duration = super::super::types::MAX_FREQUENCY + 1;
1210 assert!(!TwapOrder::new(data).is_valid());
1211 }
1212
1213 #[test]
1214 fn twap_order_is_buy_via_kind() {
1215 let data = TwapData::buy(buy_token(), sell_token(), U256::from(800u64), 4, 3600);
1216 let order = TwapOrder::new(data);
1217 assert!(order.is_buy());
1218 assert!(!order.is_sell());
1219 }
1220
1221 #[test]
1222 fn decode_params_truncated_data() {
1223 let params = ConditionalOrderParams {
1225 handler: Address::ZERO,
1226 salt: B256::ZERO,
1227 static_input: vec![0xaa; 64],
1228 };
1229 let hex = encode_params(¶ms);
1230 let truncated = &hex[..hex.len() - 10];
1232 assert!(decode_params(truncated).is_err());
1233 }
1234
1235 #[test]
1236 fn format_epoch_invalid_timestamp() {
1237 let s = format_epoch(0);
1239 assert!(s.contains("1970"));
1241 }
1242}