|
1 | 1 | use alloy::primitives::Address; |
2 | 2 | use prost::Message; |
3 | 3 | use serde::{Deserialize, Serialize}; |
4 | | -use tycho_common::Bytes; |
| 4 | +use tycho_common::{models::protocol::GetAmountOutParams, Bytes}; |
| 5 | + |
| 6 | +use crate::rfq::errors::RFQError; |
5 | 7 |
|
6 | 8 | /// Protobuf message for Bebop pricing updates |
7 | 9 | #[derive(Clone, PartialEq, Message)] |
@@ -212,6 +214,61 @@ pub struct BebopQuotePartial { |
212 | 214 | pub partial_fill_offset: u64, |
213 | 215 | } |
214 | 216 |
|
| 217 | +impl BebopQuotePartial { |
| 218 | + pub fn validate(&self, params: &GetAmountOutParams) -> Result<(), RFQError> { |
| 219 | + match &self.to_sign { |
| 220 | + BebopOrderToSign::Single(single) => { |
| 221 | + if single.taker_token != params.token_in { |
| 222 | + return Err(RFQError::FatalError(format!( |
| 223 | + "Base token mismatch: expected {}, got {}", |
| 224 | + params.token_in, single.taker_token |
| 225 | + ))); |
| 226 | + } |
| 227 | + if single.maker_token != params.token_out { |
| 228 | + return Err(RFQError::FatalError(format!( |
| 229 | + "Quote token mismatch: expected {}, got {}", |
| 230 | + params.token_out, single.maker_token |
| 231 | + ))); |
| 232 | + } |
| 233 | + if single.taker_address != params.sender { |
| 234 | + return Err(RFQError::FatalError(format!( |
| 235 | + "Taker address mismatch: expected {}, got {}", |
| 236 | + params.sender, single.taker_address |
| 237 | + ))); |
| 238 | + } |
| 239 | + if single.receiver != params.receiver { |
| 240 | + return Err(RFQError::FatalError(format!( |
| 241 | + "Receiver address mismatch: expected {}, got {}", |
| 242 | + params.receiver, single.receiver |
| 243 | + ))); |
| 244 | + } |
| 245 | + let amount_in = params.amount_in.to_string(); |
| 246 | + if single.taker_amount != amount_in { |
| 247 | + return Err(RFQError::FatalError(format!( |
| 248 | + "Base token amount mismatch: expected {}, got {}", |
| 249 | + amount_in, single.taker_amount |
| 250 | + ))); |
| 251 | + } |
| 252 | + } |
| 253 | + BebopOrderToSign::Aggregate(aggregate) => { |
| 254 | + if aggregate.taker_address != params.sender { |
| 255 | + return Err(RFQError::FatalError(format!( |
| 256 | + "Taker address mismatch: expected {}, got {}", |
| 257 | + params.sender, aggregate.taker_address |
| 258 | + ))); |
| 259 | + } |
| 260 | + if aggregate.receiver != params.receiver { |
| 261 | + return Err(RFQError::FatalError(format!( |
| 262 | + "Receiver address mismatch: expected {}, got {}", |
| 263 | + params.receiver, aggregate.receiver |
| 264 | + ))); |
| 265 | + } |
| 266 | + } |
| 267 | + } |
| 268 | + Ok(()) |
| 269 | + } |
| 270 | +} |
| 271 | + |
215 | 272 | #[derive(Debug, Clone, Serialize, Deserialize)] |
216 | 273 | #[serde(untagged)] |
217 | 274 | pub enum BebopOrderToSign { |
@@ -351,4 +408,177 @@ mod tests { |
351 | 408 | let insufficient_mid = price_data.get_mid_price(10.0); |
352 | 409 | assert_eq!(insufficient_mid, Some(2000.325)); |
353 | 410 | } |
| 411 | + |
| 412 | + #[cfg(test)] |
| 413 | + mod bebop_quote_partial_validate_tests { |
| 414 | + use std::str::FromStr; |
| 415 | + |
| 416 | + use num_bigint::BigUint; |
| 417 | + use tycho_common::models::protocol::GetAmountOutParams; |
| 418 | + |
| 419 | + use super::*; |
| 420 | + |
| 421 | + fn hex_to_bytes(hex: &str) -> Bytes { |
| 422 | + Bytes::from_str(hex).unwrap() |
| 423 | + } |
| 424 | + |
| 425 | + fn single_order() -> SingleOrderToSign { |
| 426 | + SingleOrderToSign { |
| 427 | + maker_address: hex_to_bytes("0x1111111111111111111111111111111111111111"), |
| 428 | + taker_address: hex_to_bytes("0x2222222222222222222222222222222222222222"), |
| 429 | + maker_token: hex_to_bytes("0x3333333333333333333333333333333333333333"), |
| 430 | + taker_token: hex_to_bytes("0x4444444444444444444444444444444444444444"), |
| 431 | + maker_amount: "2000".to_string(), |
| 432 | + taker_amount: "1000".to_string(), |
| 433 | + maker_nonce: "1".to_string(), |
| 434 | + expiry: 123456, |
| 435 | + receiver: hex_to_bytes("0x5555555555555555555555555555555555555555"), |
| 436 | + } |
| 437 | + } |
| 438 | + |
| 439 | + fn aggregate_order() -> AggregateOrderToSign { |
| 440 | + AggregateOrderToSign { |
| 441 | + taker_address: hex_to_bytes("0x2222222222222222222222222222222222222222"), |
| 442 | + maker_amounts: vec![vec!["2000".to_string()]], |
| 443 | + taker_amounts: vec![vec!["1000".to_string()]], |
| 444 | + expiry: 123456, |
| 445 | + receiver: hex_to_bytes("0x5555555555555555555555555555555555555555"), |
| 446 | + } |
| 447 | + } |
| 448 | + |
| 449 | + fn params() -> GetAmountOutParams { |
| 450 | + GetAmountOutParams { |
| 451 | + amount_in: BigUint::from(1000u32), |
| 452 | + token_in: hex_to_bytes("0x4444444444444444444444444444444444444444"), |
| 453 | + token_out: hex_to_bytes("0x3333333333333333333333333333333333333333"), |
| 454 | + sender: hex_to_bytes("0x2222222222222222222222222222222222222222"), |
| 455 | + receiver: hex_to_bytes("0x5555555555555555555555555555555555555555"), |
| 456 | + } |
| 457 | + } |
| 458 | + |
| 459 | + fn quote_partial_single() -> BebopQuotePartial { |
| 460 | + BebopQuotePartial { |
| 461 | + status: "success".to_string(), |
| 462 | + settlement_address: hex_to_bytes("0x9999999999999999999999999999999999999999"), |
| 463 | + tx: TxData { |
| 464 | + to: hex_to_bytes("0x8888888888888888888888888888888888888888"), |
| 465 | + data: hex_to_bytes("0x1234"), |
| 466 | + value: "0".to_string(), |
| 467 | + from: hex_to_bytes("0x7777777777777777777777777777777777777777"), |
| 468 | + gas: 21000, |
| 469 | + gas_price: 100, |
| 470 | + }, |
| 471 | + to_sign: BebopOrderToSign::Single(Box::new(single_order())), |
| 472 | + partial_fill_offset: 0, |
| 473 | + } |
| 474 | + } |
| 475 | + |
| 476 | + fn quote_partial_aggregate() -> BebopQuotePartial { |
| 477 | + BebopQuotePartial { |
| 478 | + status: "success".to_string(), |
| 479 | + settlement_address: hex_to_bytes("0x9999999999999999999999999999999999999999"), |
| 480 | + tx: TxData { |
| 481 | + to: hex_to_bytes("0x8888888888888888888888888888888888888888"), |
| 482 | + data: hex_to_bytes("0x1234"), |
| 483 | + value: "0".to_string(), |
| 484 | + from: hex_to_bytes("0x7777777777777777777777777777777777777777"), |
| 485 | + gas: 21000, |
| 486 | + gas_price: 100, |
| 487 | + }, |
| 488 | + to_sign: BebopOrderToSign::Aggregate(Box::new(aggregate_order())), |
| 489 | + partial_fill_offset: 0, |
| 490 | + } |
| 491 | + } |
| 492 | + |
| 493 | + #[test] |
| 494 | + fn test_validate_single_success() { |
| 495 | + let quote = quote_partial_single(); |
| 496 | + let params = params(); |
| 497 | + assert!(quote.validate(¶ms).is_ok()); |
| 498 | + } |
| 499 | + |
| 500 | + #[test] |
| 501 | + fn test_validate_single_base_token_mismatch() { |
| 502 | + let mut quote = quote_partial_single(); |
| 503 | + if let BebopOrderToSign::Single(ref mut single) = quote.to_sign { |
| 504 | + single.taker_token = hex_to_bytes("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); |
| 505 | + } |
| 506 | + let params = params(); |
| 507 | + let err = quote.validate(¶ms).unwrap_err(); |
| 508 | + assert!(format!("{:?}", err).contains("Base token mismatch")); |
| 509 | + } |
| 510 | + |
| 511 | + #[test] |
| 512 | + fn test_validate_single_quote_token_mismatch() { |
| 513 | + let mut quote = quote_partial_single(); |
| 514 | + if let BebopOrderToSign::Single(ref mut single) = quote.to_sign { |
| 515 | + single.maker_token = hex_to_bytes("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); |
| 516 | + } |
| 517 | + let params = params(); |
| 518 | + let err = quote.validate(¶ms).unwrap_err(); |
| 519 | + assert!(format!("{:?}", err).contains("Quote token mismatch")); |
| 520 | + } |
| 521 | + |
| 522 | + #[test] |
| 523 | + fn test_validate_single_taker_address_mismatch() { |
| 524 | + let mut quote = quote_partial_single(); |
| 525 | + if let BebopOrderToSign::Single(ref mut single) = quote.to_sign { |
| 526 | + single.taker_address = hex_to_bytes("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"); |
| 527 | + } |
| 528 | + let params = params(); |
| 529 | + let err = quote.validate(¶ms).unwrap_err(); |
| 530 | + assert!(format!("{:?}", err).contains("Taker address mismatch")); |
| 531 | + } |
| 532 | + |
| 533 | + #[test] |
| 534 | + fn test_validate_single_receiver_mismatch() { |
| 535 | + let mut quote = quote_partial_single(); |
| 536 | + if let BebopOrderToSign::Single(ref mut single) = quote.to_sign { |
| 537 | + single.receiver = hex_to_bytes("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"); |
| 538 | + } |
| 539 | + let params = params(); |
| 540 | + let err = quote.validate(¶ms).unwrap_err(); |
| 541 | + assert!(format!("{:?}", err).contains("Receiver address mismatch")); |
| 542 | + } |
| 543 | + |
| 544 | + #[test] |
| 545 | + fn test_validate_single_base_token_amount_mismatch() { |
| 546 | + let mut quote = quote_partial_single(); |
| 547 | + if let BebopOrderToSign::Single(ref mut single) = quote.to_sign { |
| 548 | + single.taker_amount = "9999".to_string(); |
| 549 | + } |
| 550 | + let params = params(); |
| 551 | + let err = quote.validate(¶ms).unwrap_err(); |
| 552 | + assert!(format!("{:?}", err).contains("Base token amount mismatch")); |
| 553 | + } |
| 554 | + |
| 555 | + #[test] |
| 556 | + fn test_validate_aggregate_success() { |
| 557 | + let quote = quote_partial_aggregate(); |
| 558 | + let params = params(); |
| 559 | + assert!(quote.validate(¶ms).is_ok()); |
| 560 | + } |
| 561 | + |
| 562 | + #[test] |
| 563 | + fn test_validate_aggregate_taker_address_mismatch() { |
| 564 | + let mut quote = quote_partial_aggregate(); |
| 565 | + if let BebopOrderToSign::Aggregate(ref mut agg) = quote.to_sign { |
| 566 | + agg.taker_address = hex_to_bytes("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"); |
| 567 | + } |
| 568 | + let params = params(); |
| 569 | + let err = quote.validate(¶ms).unwrap_err(); |
| 570 | + assert!(format!("{:?}", err).contains("Taker address mismatch")); |
| 571 | + } |
| 572 | + |
| 573 | + #[test] |
| 574 | + fn test_validate_aggregate_receiver_mismatch() { |
| 575 | + let mut quote = quote_partial_aggregate(); |
| 576 | + if let BebopOrderToSign::Aggregate(ref mut agg) = quote.to_sign { |
| 577 | + agg.receiver = hex_to_bytes("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"); |
| 578 | + } |
| 579 | + let params = params(); |
| 580 | + let err = quote.validate(¶ms).unwrap_err(); |
| 581 | + assert!(format!("{:?}", err).contains("Receiver address mismatch")); |
| 582 | + } |
| 583 | + } |
354 | 584 | } |
0 commit comments