Skip to main content

tycho_executor/phase/
credit.rs

1use anyhow::Result;
2use tycho_types::models::CreditPhase;
3
4use crate::ExecutorState;
5use crate::phase::receive::ReceivedMessage;
6
7impl ExecutorState<'_> {
8    /// Credit phase of ordinary transactions.
9    ///
10    /// - Adds the remainder of the message balance to the account balance;
11    /// - Requires calling the [`receive_in_msg`] first;
12    /// - Only makes sense for internal messages;
13    /// - Follows the storage phase when [`bounce_enabled`],
14    ///   otherwise must be called before it.
15    ///
16    /// Returns an executed [`CreditPhase`].
17    ///
18    /// Fails only on account balance overflow. This should not happen on networks
19    /// with valid value flow.
20    ///
21    /// [`receive_in_msg`]: Self::receive_in_msg
22    /// [`bounce_enabled`]: ReceivedMessage::bounce_enabled
23    pub fn credit_phase(&mut self, received: &ReceivedMessage) -> Result<CreditPhase> {
24        // Remaining message balance is added to the account balamce.
25        self.balance.try_add_assign(&received.balance_remaining)?;
26
27        // Update suspension flag.
28        if self.params.authority_marks_enabled && !self.is_special && !self.is_marks_authority {
29            self.is_suspended_by_marks = matches!(
30                &self.config.authority_marks,
31                Some(marks) if marks.is_suspended(&self.balance)?,
32            );
33        }
34
35        Ok(CreditPhase {
36            // Due payment is only collected in storage phase.
37            // For messages with bounce flag, contract always receives
38            // the amount specified in message.
39            due_fees_collected: None,
40            credit: received.balance_remaining.clone(),
41        })
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use tycho_types::cell::Cell;
48    use tycho_types::models::CurrencyCollection;
49    use tycho_types::num::Tokens;
50
51    use super::*;
52    use crate::tests::{make_default_config, make_default_params};
53
54    #[test]
55    fn credit_phase_works() {
56        let params = make_default_params();
57        let config = make_default_config();
58
59        let mut state = ExecutorState::new_uninit(
60            &params,
61            &config,
62            &Default::default(),
63            Tokens::new(1_000_000_000),
64        );
65        let prev_balance = state.balance.clone();
66        let prev_total_fees = state.total_fees;
67
68        let msg_balance = CurrencyCollection::from(Tokens::new(123_000_000_000));
69        let credit_phase = state
70            .credit_phase(&ReceivedMessage {
71                root: Cell::default(),
72                init: None,
73                body: Default::default(),
74                is_external: false,
75                bounce_enabled: false,
76                balance_remaining: msg_balance.clone(),
77            })
78            .unwrap();
79
80        // No due fees must be collected on the credit phase.
81        assert!(credit_phase.due_fees_collected.is_none());
82        // Credit must be the same as message balance.
83        assert_eq!(credit_phase.credit, msg_balance);
84        // Account balance must receive the message balance.
85        assert_eq!(
86            state.balance,
87            prev_balance.checked_add(&msg_balance).unwrap()
88        );
89        // No fees must be collected on the credit phase.
90        assert_eq!(state.total_fees, prev_total_fees);
91    }
92}