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
162#[derive(Debug, Clone)]
171pub struct SettledOrder {
172 tx_hash: Option<B256>,
173 settled_amount: Option<BigUint>,
174 gas_cost: BigUint,
175}
176
177impl SettledOrder {
178 pub(crate) fn new(
179 tx_hash: Option<B256>,
180 settled_amount: Option<BigUint>,
181 gas_cost: BigUint,
182 ) -> Self {
183 Self { tx_hash, settled_amount, gas_cost }
184 }
185
186 pub fn tx_hash(&self) -> Option<B256> {
188 self.tx_hash
189 }
190
191 pub fn settled_amount(&self) -> Option<&BigUint> {
195 self.settled_amount.as_ref()
196 }
197
198 pub fn gas_cost(&self) -> &BigUint {
200 &self.gas_cost
201 }
202}
203
204pub enum ExecutionReceipt {
222 Transaction(Pin<Box<dyn Future<Output = Result<SettledOrder, FyndError>> + Send + 'static>>),
224}
225
226impl Future for ExecutionReceipt {
227 type Output = Result<SettledOrder, FyndError>;
228
229 fn poll(
230 self: Pin<&mut Self>,
231 cx: &mut std::task::Context<'_>,
232 ) -> std::task::Poll<Self::Output> {
233 match self.get_mut() {
234 Self::Transaction(fut) => fut.as_mut().poll(cx),
235 }
236 }
237}
238
239pub struct ApprovalPayload {
248 pub(crate) tx: TxEip1559,
249 pub(crate) token: bytes::Bytes,
251 pub(crate) spender: bytes::Bytes,
253 pub(crate) amount: BigUint,
255}
256
257impl ApprovalPayload {
258 pub fn signing_hash(&self) -> B256 {
260 use alloy::consensus::SignableTransaction;
261 self.tx.signature_hash()
262 }
263
264 pub fn tx(&self) -> &TxEip1559 {
266 &self.tx
267 }
268
269 pub fn token(&self) -> &bytes::Bytes {
271 &self.token
272 }
273
274 pub fn spender(&self) -> &bytes::Bytes {
276 &self.spender
277 }
278
279 pub fn amount(&self) -> &BigUint {
281 &self.amount
282 }
283}
284
285pub struct SignedApproval {
291 payload: ApprovalPayload,
292 signature: Signature,
293}
294
295impl SignedApproval {
296 pub fn assemble(payload: ApprovalPayload, signature: Signature) -> Self {
299 Self { payload, signature }
300 }
301
302 pub fn payload(&self) -> &ApprovalPayload {
304 &self.payload
305 }
306
307 pub fn signature(&self) -> &Signature {
309 &self.signature
310 }
311
312 pub(crate) fn into_parts(self) -> (ApprovalPayload, Signature) {
313 (self.payload, self.signature)
314 }
315}
316
317#[derive(Debug, Clone)]
323pub struct MinedTx {
324 tx_hash: B256,
325 gas_cost: BigUint,
326}
327
328impl MinedTx {
329 pub(crate) fn new(tx_hash: B256, gas_cost: BigUint) -> Self {
330 Self { tx_hash, gas_cost }
331 }
332
333 pub fn tx_hash(&self) -> B256 {
335 self.tx_hash
336 }
337
338 pub fn gas_cost(&self) -> &BigUint {
340 &self.gas_cost
341 }
342}
343
344pub enum TxReceipt {
349 Pending(Pin<Box<dyn Future<Output = Result<MinedTx, FyndError>> + Send + 'static>>),
351}
352
353impl Future for TxReceipt {
354 type Output = Result<MinedTx, FyndError>;
355
356 fn poll(
357 self: Pin<&mut Self>,
358 cx: &mut std::task::Context<'_>,
359 ) -> std::task::Poll<Self::Output> {
360 match self.get_mut() {
361 Self::Pending(fut) => fut.as_mut().poll(cx),
362 }
363 }
364}
365
366pub(crate) fn compute_settled_amount(
374 receipt: &alloy::rpc::types::TransactionReceipt,
375 token_out_addr: &Address,
376 receiver_addr: &Address,
377) -> Option<BigUint> {
378 use alloy::primitives::keccak256;
379
380 let erc20_topic = keccak256(b"Transfer(address,address,uint256)");
382 let erc6909_topic = keccak256(b"Transfer(address,address,address,uint256,uint256)");
385
386 let mut total = BigUint::ZERO;
387 let mut found = false;
388
389 for log in receipt.logs() {
390 if log.address() != *token_out_addr {
391 continue;
392 }
393 let topics = log.topics();
394 if topics.is_empty() {
395 continue;
396 }
397
398 if topics[0] == erc20_topic && topics.len() >= 3 {
399 let to = Address::from_slice(&topics[2].as_slice()[12..]);
401 if to == *receiver_addr {
402 let data = &log.data().data;
403 if data.len() >= 32 {
404 let amount = BigUint::from_bytes_be(&data[0..32]);
405 total += amount;
406 found = true;
407 }
408 }
409 } else if topics[0] == erc6909_topic && topics.len() >= 3 {
410 let to = Address::from_slice(&topics[2].as_slice()[12..]);
412 if to == *receiver_addr {
413 let data = &log.data().data;
415 if data.len() >= 64 {
416 let amount = BigUint::from_bytes_be(&data[32..64]);
417 total += amount;
418 found = true;
419 }
420 }
421 }
422 }
423
424 if found {
425 Some(total)
426 } else {
427 None
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use alloy::{
434 primitives::{keccak256, Address, Bytes as AlloyBytes, LogData, B256},
435 rpc::types::{Log, TransactionReceipt},
436 };
437
438 use super::*;
439
440 fn make_receipt(logs: Vec<Log>) -> TransactionReceipt {
445 use alloy::{
446 consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom},
447 primitives::{Bloom, TxHash},
448 };
449
450 TransactionReceipt {
451 inner: ReceiptEnvelope::Eip1559(ReceiptWithBloom {
452 receipt: Receipt {
453 status: alloy::consensus::Eip658Value::Eip658(true),
454 cumulative_gas_used: 21_000,
455 logs,
456 },
457 logs_bloom: Bloom::default(),
458 }),
459 transaction_hash: TxHash::default(),
460 transaction_index: None,
461 block_hash: None,
462 block_number: None,
463 gas_used: 21_000,
464 effective_gas_price: 1,
465 blob_gas_used: None,
466 blob_gas_price: None,
467 from: Address::ZERO,
468 to: None,
469 contract_address: None,
470 }
471 }
472
473 fn erc20_topic() -> B256 {
474 keccak256(b"Transfer(address,address,uint256)")
475 }
476
477 fn erc6909_topic() -> B256 {
478 keccak256(b"Transfer(address,address,address,uint256,uint256)")
479 }
480
481 fn addr_topic(addr: Address) -> B256 {
483 let mut topic = [0u8; 32];
484 topic[12..].copy_from_slice(addr.as_slice());
485 B256::from(topic)
486 }
487
488 fn encode_u256(amount: u64) -> Vec<u8> {
490 let mut buf = [0u8; 32];
491 let bytes = amount.to_be_bytes();
492 buf[24..].copy_from_slice(&bytes);
493 buf.to_vec()
494 }
495
496 fn make_log(address: Address, topics: Vec<B256>, data: Vec<u8>) -> Log {
497 Log {
498 inner: alloy::primitives::Log {
499 address,
500 data: LogData::new_unchecked(topics, AlloyBytes::from(data)),
501 },
502 block_hash: None,
503 block_number: None,
504 block_timestamp: None,
505 transaction_hash: None,
506 transaction_index: None,
507 log_index: None,
508 removed: false,
509 }
510 }
511
512 #[test]
517 fn erc20_transfer_log_matched() {
518 let token = Address::with_last_byte(0x01);
519 let from = Address::with_last_byte(0x02);
520 let receiver = Address::with_last_byte(0x03);
521
522 let log = make_log(
523 token,
524 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
525 encode_u256(500),
526 );
527 let receipt = make_receipt(vec![log]);
528
529 let result = compute_settled_amount(&receipt, &token, &receiver);
530 assert_eq!(result, Some(BigUint::from(500u64)));
531 }
532
533 #[test]
534 fn erc20_transfer_log_wrong_token() {
535 let token = Address::with_last_byte(0x01);
536 let other_token = Address::with_last_byte(0x99);
537 let receiver = Address::with_last_byte(0x03);
538
539 let log = make_log(
540 other_token, vec![erc20_topic(), addr_topic(Address::ZERO), addr_topic(receiver)],
542 encode_u256(500),
543 );
544 let receipt = make_receipt(vec![log]);
545
546 let result = compute_settled_amount(&receipt, &token, &receiver);
547 assert!(result.is_none());
548 }
549
550 #[test]
551 fn erc20_transfer_log_wrong_receiver() {
552 let token = Address::with_last_byte(0x01);
553 let receiver = Address::with_last_byte(0x03);
554 let other_receiver = Address::with_last_byte(0x04);
555
556 let log = make_log(
557 token,
558 vec![erc20_topic(), addr_topic(Address::ZERO), addr_topic(other_receiver)],
559 encode_u256(500),
560 );
561 let receipt = make_receipt(vec![log]);
562
563 let result = compute_settled_amount(&receipt, &token, &receiver);
564 assert!(result.is_none());
565 }
566
567 #[test]
572 fn erc6909_transfer_log_matched() {
573 let token = Address::with_last_byte(0x10);
574 let from = Address::with_last_byte(0x11);
575 let receiver = Address::with_last_byte(0x12);
576
577 let mut data = [0u8; 64];
579 data[12..32].copy_from_slice(Address::with_last_byte(0xca).as_slice());
581 let amount_bytes = encode_u256(750);
583 data[32..].copy_from_slice(&amount_bytes);
584
585 let log = make_log(
586 token,
587 vec![erc6909_topic(), addr_topic(from), addr_topic(receiver)],
588 data.to_vec(),
589 );
590 let receipt = make_receipt(vec![log]);
591
592 let result = compute_settled_amount(&receipt, &token, &receiver);
593 assert_eq!(result, Some(BigUint::from(750u64)));
594 }
595
596 #[test]
601 fn no_matching_logs_returns_none() {
602 let token = Address::with_last_byte(0x01);
603 let receiver = Address::with_last_byte(0x03);
604
605 let unrelated_topic = keccak256(b"Approval(address,address,uint256)");
607 let log = make_log(
608 token,
609 vec![unrelated_topic, addr_topic(Address::ZERO), addr_topic(receiver)],
610 encode_u256(100),
611 );
612 let receipt = make_receipt(vec![log]);
613
614 let result = compute_settled_amount(&receipt, &token, &receiver);
615 assert!(result.is_none());
616 }
617
618 #[test]
619 fn empty_logs_returns_none() {
620 let token = Address::with_last_byte(0x01);
621 let receiver = Address::with_last_byte(0x03);
622 let receipt = make_receipt(vec![]);
623 assert!(compute_settled_amount(&receipt, &token, &receiver).is_none());
624 }
625
626 #[test]
627 fn multiple_matching_logs_amounts_summed() {
628 let token = Address::with_last_byte(0x01);
629 let from = Address::with_last_byte(0x02);
630 let receiver = Address::with_last_byte(0x03);
631
632 let log1 = make_log(
633 token,
634 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
635 encode_u256(100),
636 );
637 let log2 = make_log(
638 token,
639 vec![erc20_topic(), addr_topic(from), addr_topic(receiver)],
640 encode_u256(200),
641 );
642 let log3 = make_log(
644 token,
645 vec![erc20_topic(), addr_topic(from), addr_topic(Address::with_last_byte(0xff))],
646 encode_u256(999),
647 );
648 let receipt = make_receipt(vec![log1, log2, log3]);
649
650 let result = compute_settled_amount(&receipt, &token, &receiver);
651 assert_eq!(result, Some(BigUint::from(300u64)));
652 }
653}