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        Ok(CreditPhase {
28            // Due payment is only collected in storage phase.
29            // For messages with bounce flag, contract always receives
30            // the amount specified in message.
31            due_fees_collected: None,
32            credit: received.balance_remaining.clone(),
33        })
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use tycho_types::cell::Cell;
40    use tycho_types::models::CurrencyCollection;
41    use tycho_types::num::Tokens;
42
43    use super::*;
44    use crate::tests::{make_default_config, make_default_params};
45
46    #[test]
47    fn credit_phase_works() {
48        let params = make_default_params();
49        let config = make_default_config();
50
51        let mut state = ExecutorState::new_uninit(
52            &params,
53            &config,
54            &Default::default(),
55            Tokens::new(1_000_000_000),
56        );
57        let prev_balance = state.balance.clone();
58        let prev_total_fees = state.total_fees;
59
60        let msg_balance = CurrencyCollection::from(Tokens::new(123_000_000_000));
61        let credit_phase = state
62            .credit_phase(&ReceivedMessage {
63                root: Cell::default(),
64                init: None,
65                body: Default::default(),
66                is_external: false,
67                bounce_enabled: false,
68                balance_remaining: msg_balance.clone(),
69            })
70            .unwrap();
71
72        // No due fees must be collected on the credit phase.
73        assert!(credit_phase.due_fees_collected.is_none());
74        // Credit must be the same as message balance.
75        assert_eq!(credit_phase.credit, msg_balance);
76        // Account balance must receive the message balance.
77        assert_eq!(
78            state.balance,
79            prev_balance.checked_add(&msg_balance).unwrap()
80        );
81        // No fees must be collected on the credit phase.
82        assert_eq!(state.total_fees, prev_total_fees);
83    }
84}