1use std::{future::Future, pin::Pin};
2
3use alloy::{
4 consensus::{TxEip1559, TypedTransaction},
5 dyn_abi::TypedData,
6 primitives::{Address, Signature, B256},
7};
8use num_bigint::BigUint;
9
10use crate::{error::FyndError, Quote};
11
12#[derive(Debug)]
21pub struct FyndPayload {
22 quote: Quote,
23 tx: TypedTransaction,
24}
25
26impl FyndPayload {
27 pub(crate) fn new(quote: Quote, tx: TypedTransaction) -> Self {
28 Self { quote, tx }
29 }
30
31 pub fn quote(&self) -> &Quote {
33 &self.quote
34 }
35
36 pub fn tx(&self) -> &TypedTransaction {
40 &self.tx
41 }
42
43 pub(crate) fn into_parts(self) -> (Quote, TypedTransaction) {
45 (self.quote, self.tx)
46 }
47}
48
49#[derive(Debug)]
51pub struct TurbinePayload {
52 #[allow(dead_code)]
53 _order_quote: (),
55}
56
57#[derive(Debug)]
65pub enum SwapPayload {
66 Fynd(Box<FyndPayload>),
68 Turbine(TurbinePayload),
70}
71
72impl SwapPayload {
73 pub fn signing_hash(&self) -> B256 {
81 match self {
82 Self::Fynd(p) => {
83 use alloy::consensus::SignableTransaction;
84 p.tx.signature_hash()
85 }
86 Self::Turbine(_) => unimplemented!("Turbine signing not yet implemented"),
87 }
88 }
89
90 pub fn typed_data(&self) -> Option<&TypedData> {
95 match self {
97 Self::Fynd(_) | Self::Turbine(_) => None,
98 }
99 }
100
101 pub fn quote(&self) -> &Quote {
107 match self {
108 Self::Fynd(p) => &p.quote,
109 Self::Turbine(_) => unimplemented!("Turbine signing not yet implemented"),
110 }
111 }
112
113 pub(crate) fn into_fynd_parts(
115 self,
116 ) -> Result<(Quote, TypedTransaction), crate::error::FyndError> {
117 match self {
118 Self::Fynd(p) => Ok(p.into_parts()),
119 Self::Turbine(_) => Err(crate::error::FyndError::Protocol(
120 "Turbine execution not yet implemented".into(),
121 )),
122 }
123 }
124}
125
126pub struct SignedSwap {
136 payload: SwapPayload,
137 signature: Signature,
138}
139
140impl SignedSwap {
141 pub fn assemble(payload: SwapPayload, signature: Signature) -> Self {
144 Self { payload, signature }
145 }
146
147 pub fn payload(&self) -> &SwapPayload {
149 &self.payload
150 }
151
152 pub fn signature(&self) -> &Signature {
154 &self.signature
155 }
156
157 pub(crate) fn into_parts(self) -> (SwapPayload, Signature) {
158 (self.payload, self.signature)
159 }
160}
161
162pub struct SettledOrder {
171 settled_amount: Option<BigUint>,
172 gas_cost: BigUint,
173}
174
175impl SettledOrder {
176 pub(crate) fn new(settled_amount: Option<BigUint>, gas_cost: BigUint) -> Self {
177 Self { settled_amount, gas_cost }
178 }
179
180 pub fn settled_amount(&self) -> Option<&BigUint> {
184 self.settled_amount.as_ref()
185 }
186
187 pub fn gas_cost(&self) -> &BigUint {
189 &self.gas_cost
190 }
191}
192
193pub enum ExecutionReceipt {
211 Transaction(Pin<Box<dyn Future<Output = Result<SettledOrder, FyndError>> + Send + 'static>>),
213}
214
215impl Future for ExecutionReceipt {
216 type Output = Result<SettledOrder, FyndError>;
217
218 fn poll(
219 self: Pin<&mut Self>,
220 cx: &mut std::task::Context<'_>,
221 ) -> std::task::Poll<Self::Output> {
222 match self.get_mut() {
223 Self::Transaction(fut) => fut.as_mut().poll(cx),
224 }
225 }
226}
227
228pub struct ApprovalPayload {
237 pub(crate) tx: TxEip1559,
238 pub(crate) token: bytes::Bytes,
240 pub(crate) spender: bytes::Bytes,
242 pub(crate) amount: BigUint,
244}
245
246impl ApprovalPayload {
247 pub fn signing_hash(&self) -> B256 {
249 use alloy::consensus::SignableTransaction;
250 self.tx.signature_hash()
251 }
252
253 pub fn tx(&self) -> &TxEip1559 {
255 &self.tx
256 }
257
258 pub fn token(&self) -> &bytes::Bytes {
260 &self.token
261 }
262
263 pub fn spender(&self) -> &bytes::Bytes {
265 &self.spender
266 }
267
268 pub fn amount(&self) -> &BigUint {
270 &self.amount
271 }
272}
273
274pub struct SignedApproval {
280 payload: ApprovalPayload,
281 signature: Signature,
282}
283
284impl SignedApproval {
285 pub fn assemble(payload: ApprovalPayload, signature: Signature) -> Self {
288 Self { payload, signature }
289 }
290
291 pub fn payload(&self) -> &ApprovalPayload {
293 &self.payload
294 }
295
296 pub fn signature(&self) -> &Signature {
298 &self.signature
299 }
300
301 pub(crate) fn into_parts(self) -> (ApprovalPayload, Signature) {
302 (self.payload, self.signature)
303 }
304}
305
306pub struct MinedTx {
312 tx_hash: B256,
313 gas_cost: BigUint,
314}
315
316impl MinedTx {
317 pub(crate) fn new(tx_hash: B256, gas_cost: BigUint) -> Self {
318 Self { tx_hash, gas_cost }
319 }
320
321 pub fn tx_hash(&self) -> B256 {
323 self.tx_hash
324 }
325
326 pub fn gas_cost(&self) -> &BigUint {
328 &self.gas_cost
329 }
330}
331
332pub enum TxReceipt {
337 Pending(Pin<Box<dyn Future<Output = Result<MinedTx, FyndError>> + Send + 'static>>),
339}
340
341impl Future for TxReceipt {
342 type Output = Result<MinedTx, FyndError>;
343
344 fn poll(
345 self: Pin<&mut Self>,
346 cx: &mut std::task::Context<'_>,
347 ) -> std::task::Poll<Self::Output> {
348 match self.get_mut() {
349 Self::Pending(fut) => fut.as_mut().poll(cx),
350 }
351 }
352}
353
354pub(crate) fn compute_settled_amount(
362 receipt: &alloy::rpc::types::TransactionReceipt,
363 token_out_addr: &Address,
364 receiver_addr: &Address,
365) -> Option<BigUint> {
366 use alloy::primitives::keccak256;
367
368 let erc20_topic = keccak256(b"Transfer(address,address,uint256)");
370 let erc6909_topic = keccak256(b"Transfer(address,address,address,uint256,uint256)");
373
374 let mut total = BigUint::ZERO;
375 let mut found = false;
376
377 for log in receipt.logs() {
378 if log.address() != *token_out_addr {
379 continue;
380 }
381 let topics = log.topics();
382 if topics.is_empty() {
383 continue;
384 }
385
386 if topics[0] == erc20_topic && topics.len() >= 3 {
387 let to = Address::from_slice(&topics[2].as_slice()[12..]);
389 if to == *receiver_addr {
390 let data = &log.data().data;
391 if data.len() >= 32 {
392 let amount = BigUint::from_bytes_be(&data[0..32]);
393 total += amount;
394 found = true;
395 }
396 }
397 } else if topics[0] == erc6909_topic && topics.len() >= 3 {
398 let to = Address::from_slice(&topics[2].as_slice()[12..]);
400 if to == *receiver_addr {
401 let data = &log.data().data;
403 if data.len() >= 64 {
404 let amount = BigUint::from_bytes_be(&data[32..64]);
405 total += amount;
406 found = true;
407 }
408 }
409 }
410 }
411
412 if found {
413 Some(total)
414 } else {
415 None
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use alloy::{
422 primitives::{keccak256, Address, Bytes as AlloyBytes, LogData, B256},
423 rpc::types::{Log, TransactionReceipt},
424 };
425
426 use super::*;
427
428 fn make_receipt(logs: Vec<Log>) -> TransactionReceipt {
433 use alloy::{
434 consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom},
435 primitives::{Bloom, TxHash},
436 };
437
438 TransactionReceipt {
439 inner: ReceiptEnvelope::Eip1559(ReceiptWithBloom {
440 receipt: Receipt {
441 status: alloy::consensus::Eip658Value::Eip658(true),
442 cumulative_gas_used: 21_000,
443 logs,
444 },
445 logs_bloom: Bloom::default(),
446 }),
447 transaction_hash: TxHash::default(),
448 transaction_index: None,
449 block_hash: None,
450 block_number: None,
451 gas_used: 21_000,
452 effective_gas_price: 1,
453 blob_gas_used: None,
454 blob_gas_price: None,
455 from: Address::ZERO,
456 to: None,
457 contract_address: None,
458 }
459 }
460
461 fn erc20_topic() -> B256 {
462 keccak256(b"Transfer(address,address,uint256)")
463 }
464
465 fn erc6909_topic() -> B256 {
466 keccak256(b"Transfer(address,address,address,uint256,uint256)")
467 }
468
469 fn addr_topic(addr: Address) -> B256 {
471 let mut topic = [0u8; 32];
472 topic[12..].copy_from_slice(addr.as_slice());
473 B256::from(topic)
474 }
475
476 fn encode_u256(amount: u64) -> Vec<u8> {
478 let mut buf = [0u8; 32];
479 let bytes = amount.to_be_bytes();
480 buf[24..].copy_from_slice(&bytes);
481 buf.to_vec()
482 }
483
484 fn make_log(address: Address, topics: Vec<B256>, data: Vec<u8>) -> Log {
485 Log {
486 inner: alloy::primitives::Log {
487 address,
488 data: LogData::new_unchecked(topics, AlloyBytes::from(data)),
489 },
490 block_hash: None,
491 block_number: None,
492 block_timestamp: None,
493 transaction_hash: None,
494 transaction_index: None,
495 log_index: None,
496 removed: false,
497 }
498 }
499
500 #[test]
505 fn erc20_transfer_log_matched() {
506 let token = Address::with_last_byte(0x01);
507 let from = Address::with_last_byte(0x02);
508 let receiver = Address::with_last_byte(0x03);
509
510 let log = make_log(
511 token,
512 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
513 encode_u256(500),
514 );
515 let receipt = make_receipt(vec![log]);
516
517 let result = compute_settled_amount(&receipt, &token, &receiver);
518 assert_eq!(result, Some(BigUint::from(500u64)));
519 }
520
521 #[test]
522 fn erc20_transfer_log_wrong_token() {
523 let token = Address::with_last_byte(0x01);
524 let other_token = Address::with_last_byte(0x99);
525 let receiver = Address::with_last_byte(0x03);
526
527 let log = make_log(
528 other_token, vec![erc20_topic(), addr_topic(Address::ZERO), addr_topic(receiver)],
530 encode_u256(500),
531 );
532 let receipt = make_receipt(vec![log]);
533
534 let result = compute_settled_amount(&receipt, &token, &receiver);
535 assert!(result.is_none());
536 }
537
538 #[test]
539 fn erc20_transfer_log_wrong_receiver() {
540 let token = Address::with_last_byte(0x01);
541 let receiver = Address::with_last_byte(0x03);
542 let other_receiver = Address::with_last_byte(0x04);
543
544 let log = make_log(
545 token,
546 vec![erc20_topic(), addr_topic(Address::ZERO), addr_topic(other_receiver)],
547 encode_u256(500),
548 );
549 let receipt = make_receipt(vec![log]);
550
551 let result = compute_settled_amount(&receipt, &token, &receiver);
552 assert!(result.is_none());
553 }
554
555 #[test]
560 fn erc6909_transfer_log_matched() {
561 let token = Address::with_last_byte(0x10);
562 let from = Address::with_last_byte(0x11);
563 let receiver = Address::with_last_byte(0x12);
564
565 let mut data = [0u8; 64];
567 data[12..32].copy_from_slice(Address::with_last_byte(0xca).as_slice());
569 let amount_bytes = encode_u256(750);
571 data[32..].copy_from_slice(&amount_bytes);
572
573 let log = make_log(
574 token,
575 vec![erc6909_topic(), addr_topic(from), addr_topic(receiver)],
576 data.to_vec(),
577 );
578 let receipt = make_receipt(vec![log]);
579
580 let result = compute_settled_amount(&receipt, &token, &receiver);
581 assert_eq!(result, Some(BigUint::from(750u64)));
582 }
583
584 #[test]
589 fn no_matching_logs_returns_none() {
590 let token = Address::with_last_byte(0x01);
591 let receiver = Address::with_last_byte(0x03);
592
593 let unrelated_topic = keccak256(b"Approval(address,address,uint256)");
595 let log = make_log(
596 token,
597 vec![unrelated_topic, addr_topic(Address::ZERO), addr_topic(receiver)],
598 encode_u256(100),
599 );
600 let receipt = make_receipt(vec![log]);
601
602 let result = compute_settled_amount(&receipt, &token, &receiver);
603 assert!(result.is_none());
604 }
605
606 #[test]
607 fn empty_logs_returns_none() {
608 let token = Address::with_last_byte(0x01);
609 let receiver = Address::with_last_byte(0x03);
610 let receipt = make_receipt(vec![]);
611 assert!(compute_settled_amount(&receipt, &token, &receiver).is_none());
612 }
613
614 #[test]
615 fn multiple_matching_logs_amounts_summed() {
616 let token = Address::with_last_byte(0x01);
617 let from = Address::with_last_byte(0x02);
618 let receiver = Address::with_last_byte(0x03);
619
620 let log1 = make_log(
621 token,
622 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
623 encode_u256(100),
624 );
625 let log2 = make_log(
626 token,
627 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
628 encode_u256(200),
629 );
630 let log3 = make_log(
632 token,
633 vec![erc20_topic(), addr_topic(from), addr_topic(Address::with_last_byte(0xff))],
634 encode_u256(999),
635 );
636 let receipt = make_receipt(vec![log1, log2, log3]);
637
638 let result = compute_settled_amount(&receipt, &token, &receiver);
639 assert_eq!(result, Some(BigUint::from(300u64)));
640 }
641}