tycho_types/models/transaction/
mod.rs

1//! Transaction models.
2
3pub use self::phases::*;
4use crate::cell::*;
5use crate::dict::{self, Dict};
6use crate::error::*;
7use crate::models::account::AccountStatus;
8use crate::models::currency::CurrencyCollection;
9use crate::models::message::Message;
10use crate::num::*;
11
12mod phases;
13
14#[cfg(test)]
15mod tests;
16
17/// Blockchain transaction.
18#[derive(Debug, Clone, Eq, PartialEq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Transaction {
21    /// Account on which this transaction was produced.
22    pub account: HashBytes,
23    /// Logical time when the transaction was created.
24    pub lt: u64,
25    /// The hash of the previous transaction on the same account.
26    pub prev_trans_hash: HashBytes,
27    /// The logical time of the previous transaction on the same account.
28    pub prev_trans_lt: u64,
29    /// Unix timestamp when the transaction was created.
30    pub now: u32,
31    /// The number of outgoing messages.
32    pub out_msg_count: Uint15,
33    /// Account status before this transaction.
34    pub orig_status: AccountStatus,
35    /// Account status after this transaction.
36    pub end_status: AccountStatus,
37    /// Optional incoming message.
38    #[cfg_attr(feature = "serde", serde(with = "serde_in_msg"))]
39    pub in_msg: Option<Cell>,
40    /// Outgoing messages.
41    #[cfg_attr(feature = "serde", serde(with = "serde_out_msgs"))]
42    pub out_msgs: Dict<Uint15, Cell>,
43    /// Total transaction fees (including extra fwd fees).
44    pub total_fees: CurrencyCollection,
45    /// Account state hashes.
46    pub state_update: Lazy<HashUpdate>,
47    /// Detailed transaction info.
48    pub info: Lazy<TxInfo>,
49}
50
51impl Transaction {
52    /// Tries to load the incoming message, if present.
53    pub fn load_in_msg(&self) -> Result<Option<Message<'_>>, Error> {
54        match &self.in_msg {
55            Some(in_msg) => match in_msg.parse::<Message>() {
56                Ok(message) => Ok(Some(message)),
57                Err(e) => Err(e),
58            },
59            None => Ok(None),
60        }
61    }
62
63    /// Tries to load the detailed transaction info from the lazy cell.
64    pub fn load_info(&self) -> Result<TxInfo, Error> {
65        self.info.load()
66    }
67}
68
69impl Transaction {
70    /// Gets an iterator over the output messages of this transaction, in order by lt.
71    /// The iterator element type is `Result<Message<'a>>`.
72    ///
73    /// If the dictionary or message is invalid, finishes after the first invalid element,
74    /// returning an error.
75    pub fn iter_out_msgs(&'_ self) -> TxOutMsgIter<'_> {
76        TxOutMsgIter {
77            inner: self.out_msgs.raw_values(),
78        }
79    }
80}
81
82#[cfg(feature = "serde")]
83mod serde_in_msg {
84    use super::*;
85
86    pub fn serialize<S>(in_msg: &Option<Cell>, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: serde::Serializer,
89    {
90        if serializer.is_human_readable() {
91            match in_msg {
92                Some(in_msg) => {
93                    let message = ok!(in_msg.parse::<Message>().map_err(serde::ser::Error::custom));
94                    serializer.serialize_some(&message)
95                }
96                None => serializer.serialize_none(),
97            }
98        } else {
99            crate::boc::Boc::serialize(in_msg, serializer)
100        }
101    }
102
103    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Cell>, D::Error>
104    where
105        D: serde::Deserializer<'de>,
106    {
107        use serde::de::{Deserialize, Error};
108
109        if deserializer.is_human_readable() {
110            match ok!(Option::<crate::models::OwnedMessage>::deserialize(
111                deserializer
112            )) {
113                Some(message) => CellBuilder::build_from(&message)
114                    .map_err(Error::custom)
115                    .map(Some),
116                None => Ok(None),
117            }
118        } else {
119            crate::boc::Boc::deserialize(deserializer)
120        }
121    }
122}
123
124#[cfg(feature = "serde")]
125mod serde_out_msgs {
126    use super::*;
127
128    pub fn serialize<S>(out_msgs: &Dict<Uint15, Cell>, serializer: S) -> Result<S::Ok, S::Error>
129    where
130        S: serde::Serializer,
131    {
132        use serde::ser::{Error, SerializeMap};
133
134        if serializer.is_human_readable() {
135            let mut map = ok!(serializer.serialize_map(None));
136            for entry in out_msgs.iter() {
137                match entry {
138                    Ok((key, value)) => {
139                        let message = ok!(value.parse::<Message>().map_err(Error::custom));
140                        ok!(map.serialize_entry(&key, &message));
141                    }
142                    Err(e) => return Err(Error::custom(e)),
143                }
144            }
145            map.end()
146        } else {
147            crate::boc::BocRepr::serialize(out_msgs, serializer)
148        }
149    }
150
151    pub fn deserialize<'de, D>(deserializer: D) -> Result<Dict<Uint15, Cell>, D::Error>
152    where
153        D: serde::Deserializer<'de>,
154    {
155        use serde::de::{Deserialize, Error};
156
157        if deserializer.is_human_readable() {
158            let messages = ok!(
159                ahash::HashMap::<Uint15, crate::models::OwnedMessage>::deserialize(deserializer)
160            );
161
162            let cx = Cell::empty_context();
163            let mut dict = Dict::new();
164            for (key, value) in &messages {
165                let cell = ok!(CellBuilder::build_from(value).map_err(Error::custom));
166                ok!(dict.set_ext(*key, cell, cx).map_err(Error::custom));
167            }
168            Ok(dict)
169        } else {
170            crate::boc::BocRepr::deserialize(deserializer)
171        }
172    }
173}
174
175/// An iterator over the transaction output messages.
176///
177/// This struct is created by the [`iter_out_msgs`] method on [`Transaction`].
178/// See its documentation for more.
179///
180/// [`iter_out_msgs`]: Transaction::iter_out_msgs
181#[derive(Clone)]
182pub struct TxOutMsgIter<'a> {
183    inner: dict::RawValues<'a>,
184}
185
186impl<'a> Iterator for TxOutMsgIter<'a> {
187    type Item = Result<Message<'a>, Error>;
188
189    fn next(&mut self) -> Option<Self::Item> {
190        match self.inner.next()? {
191            Ok(mut value) => {
192                let e = match value.load_reference_as_slice() {
193                    Ok(mut value) => match Message::<'a>::load_from(&mut value) {
194                        Ok(message) => return Some(Ok(message)),
195                        Err(e) => e,
196                    },
197                    Err(e) => e,
198                };
199
200                Some(Err(self.inner.finish(e)))
201            }
202            Err(e) => Some(Err(e)),
203        }
204    }
205}
206
207impl Transaction {
208    const TAG: u8 = 0b0111;
209}
210
211impl Store for Transaction {
212    fn store_into(
213        &self,
214        builder: &mut CellBuilder,
215        context: &dyn CellContext,
216    ) -> Result<(), Error> {
217        let messages = {
218            let mut builder = CellBuilder::new();
219            ok!(self.in_msg.store_into(&mut builder, context));
220            ok!(self.out_msgs.store_into(&mut builder, context));
221            ok!(builder.build_ext(context))
222        };
223
224        ok!(builder.store_small_uint(Self::TAG, 4));
225        ok!(builder.store_u256(&self.account));
226        ok!(builder.store_u64(self.lt));
227        ok!(builder.store_u256(&self.prev_trans_hash));
228        ok!(builder.store_u64(self.prev_trans_lt));
229        ok!(builder.store_u32(self.now));
230        ok!(self.out_msg_count.store_into(builder, context));
231        ok!(self.orig_status.store_into(builder, context));
232        ok!(self.end_status.store_into(builder, context));
233        ok!(builder.store_reference(messages));
234        ok!(self.total_fees.store_into(builder, context));
235        ok!(self.state_update.store_into(builder, context));
236        self.info.store_into(builder, context)
237    }
238}
239
240impl<'a> Load<'a> for Transaction {
241    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
242        match slice.load_small_uint(4) {
243            Ok(Self::TAG) => {}
244            Ok(_) => return Err(Error::InvalidTag),
245            Err(e) => return Err(e),
246        }
247
248        let (in_msg, out_msgs) = {
249            let slice = &mut ok!(slice.load_reference_as_slice());
250            let in_msg = ok!(Option::<Cell>::load_from(slice));
251            let out_msgs = ok!(Dict::load_from(slice));
252            (in_msg, out_msgs)
253        };
254
255        Ok(Self {
256            account: ok!(slice.load_u256()),
257            lt: ok!(slice.load_u64()),
258            prev_trans_hash: ok!(slice.load_u256()),
259            prev_trans_lt: ok!(slice.load_u64()),
260            now: ok!(slice.load_u32()),
261            out_msg_count: ok!(Uint15::load_from(slice)),
262            orig_status: ok!(AccountStatus::load_from(slice)),
263            end_status: ok!(AccountStatus::load_from(slice)),
264            in_msg,
265            out_msgs,
266            total_fees: ok!(CurrencyCollection::load_from(slice)),
267            state_update: ok!(Lazy::<HashUpdate>::load_from(slice)),
268            info: ok!(Lazy::<TxInfo>::load_from(slice)),
269        })
270    }
271}
272
273/// Detailed transaction info.
274#[derive(Debug, Clone, Eq, PartialEq)]
275#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
276#[cfg_attr(feature = "serde", serde(tag = "ty"))]
277pub enum TxInfo {
278    /// Ordinary transaction info.
279    Ordinary(OrdinaryTxInfo),
280    /// Tick-tock transaction info.
281    TickTock(TickTockTxInfo),
282}
283
284impl Store for TxInfo {
285    fn store_into(
286        &self,
287        builder: &mut CellBuilder,
288        context: &dyn CellContext,
289    ) -> Result<(), Error> {
290        match self {
291            Self::Ordinary(info) => {
292                ok!(builder.store_small_uint(0b0000, 4));
293                info.store_into(builder, context)
294            }
295            Self::TickTock(info) => {
296                ok!(builder.store_small_uint(0b001, 3));
297                info.store_into(builder, context)
298            }
299        }
300    }
301}
302
303impl<'a> Load<'a> for TxInfo {
304    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
305        let tag_part = ok!(slice.load_small_uint(3));
306        Ok(if tag_part == 0b001 {
307            match TickTockTxInfo::load_from(slice) {
308                Ok(info) => Self::TickTock(info),
309                Err(e) => return Err(e),
310            }
311        } else if tag_part == 0b000 && !ok!(slice.load_bit()) {
312            match OrdinaryTxInfo::load_from(slice) {
313                Ok(info) => Self::Ordinary(info),
314                Err(e) => return Err(e),
315            }
316        } else {
317            return Err(Error::InvalidTag);
318        })
319    }
320}
321
322impl From<OrdinaryTxInfo> for TxInfo {
323    #[inline]
324    fn from(value: OrdinaryTxInfo) -> Self {
325        Self::Ordinary(value)
326    }
327}
328
329impl From<TickTockTxInfo> for TxInfo {
330    #[inline]
331    fn from(value: TickTockTxInfo) -> Self {
332        Self::TickTock(value)
333    }
334}
335
336/// Ordinary transaction info.
337#[derive(Debug, Clone, Eq, PartialEq)]
338#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
339pub struct OrdinaryTxInfo {
340    /// Whether the credit phase was executed first
341    /// (usually set when incoming message has `bounce: false`).
342    pub credit_first: bool,
343    /// Storage phase info.
344    ///
345    /// Skipped if the account did not exist prior to execution.
346    pub storage_phase: Option<StoragePhase>,
347    /// Credit phase info.
348    ///
349    /// Skipped if the incoming message is external.
350    pub credit_phase: Option<CreditPhase>,
351    /// Compute phase info.
352    pub compute_phase: ComputePhase,
353    /// Action phase info.
354    ///
355    /// Skipped if the transaction was aborted at the compute phase.
356    pub action_phase: Option<ActionPhase>,
357    /// Whether the transaction was reverted.
358    pub aborted: bool,
359    /// Bounce phase info.
360    ///
361    /// Only present if the incoming message had `bounce: true` and
362    /// the compute phase failed.
363    pub bounce_phase: Option<BouncePhase>,
364    /// Whether the account was destroyed during this transaction.
365    pub destroyed: bool,
366}
367
368impl Store for OrdinaryTxInfo {
369    fn store_into(
370        &self,
371        builder: &mut CellBuilder,
372        context: &dyn CellContext,
373    ) -> Result<(), Error> {
374        let action_phase = match &self.action_phase {
375            Some(action_phase) => {
376                let mut builder = CellBuilder::new();
377                ok!(action_phase.store_into(&mut builder, context));
378                Some(ok!(builder.build_ext(context)))
379            }
380            None => None,
381        };
382
383        ok!(builder.store_bit(self.credit_first));
384        ok!(self.storage_phase.store_into(builder, context));
385        ok!(self.credit_phase.store_into(builder, context));
386        ok!(self.compute_phase.store_into(builder, context));
387        ok!(action_phase.store_into(builder, context));
388        ok!(builder.store_bit(self.aborted));
389        ok!(self.bounce_phase.store_into(builder, context));
390        builder.store_bit(self.destroyed)
391    }
392}
393
394impl<'a> Load<'a> for OrdinaryTxInfo {
395    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
396        Ok(Self {
397            credit_first: ok!(slice.load_bit()),
398            storage_phase: ok!(Option::<StoragePhase>::load_from(slice)),
399            credit_phase: ok!(Option::<CreditPhase>::load_from(slice)),
400            compute_phase: ok!(ComputePhase::load_from(slice)),
401            action_phase: match ok!(Option::<Cell>::load_from(slice)) {
402                Some(cell) => Some(ok!(cell.as_ref().parse::<ActionPhase>())),
403                None => None,
404            },
405            aborted: ok!(slice.load_bit()),
406            bounce_phase: ok!(Option::<BouncePhase>::load_from(slice)),
407            destroyed: ok!(slice.load_bit()),
408        })
409    }
410}
411
412/// Tick-tock transaction info.
413#[derive(Debug, Clone, Eq, PartialEq)]
414#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
415pub struct TickTockTxInfo {
416    /// Tick-tock transaction execution edge.
417    pub kind: TickTock,
418    /// Storage phase info.
419    pub storage_phase: StoragePhase,
420    /// Compute phase info.
421    pub compute_phase: ComputePhase,
422    /// Action phase info.
423    ///
424    /// Skipped if the transaction was aborted at the compute phase.
425    pub action_phase: Option<ActionPhase>,
426    /// Whether the transaction was reverted.
427    pub aborted: bool,
428    /// Whether the account was destroyed during this transaction.
429    pub destroyed: bool,
430}
431
432impl Store for TickTockTxInfo {
433    fn store_into(
434        &self,
435        builder: &mut CellBuilder,
436        context: &dyn CellContext,
437    ) -> Result<(), Error> {
438        let action_phase = match &self.action_phase {
439            Some(action_phase) => {
440                let mut builder = CellBuilder::new();
441                ok!(action_phase.store_into(&mut builder, context));
442                Some(ok!(builder.build_ext(context)))
443            }
444            None => None,
445        };
446
447        let flags = ((self.aborted as u8) << 1) | (self.destroyed as u8);
448
449        ok!(self.kind.store_into(builder, context));
450        ok!(self.storage_phase.store_into(builder, context));
451        ok!(self.compute_phase.store_into(builder, context));
452        ok!(action_phase.store_into(builder, context));
453        builder.store_small_uint(flags, 2)
454    }
455}
456
457impl<'a> Load<'a> for TickTockTxInfo {
458    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
459        let kind = ok!(TickTock::load_from(slice));
460        let storage_phase = ok!(StoragePhase::load_from(slice));
461        let compute_phase = ok!(ComputePhase::load_from(slice));
462        let action_phase = match ok!(Option::<Cell>::load_from(slice)) {
463            Some(cell) => Some(ok!(cell.as_ref().parse::<ActionPhase>())),
464            None => None,
465        };
466        let flags = ok!(slice.load_small_uint(2));
467
468        Ok(Self {
469            kind,
470            storage_phase,
471            compute_phase,
472            action_phase,
473            aborted: flags & 0b10 != 0,
474            destroyed: flags & 0b01 != 0,
475        })
476    }
477}
478
479/// Tick-tock transaction execution edge.
480#[derive(Debug, Copy, Clone, Eq, PartialEq)]
481#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
482pub enum TickTock {
483    /// Start of the block.
484    Tick = 0,
485    /// End of the block.
486    Tock = 1,
487}
488
489impl Store for TickTock {
490    #[inline]
491    fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> {
492        builder.store_bit(*self == Self::Tock)
493    }
494}
495
496impl<'a> Load<'a> for TickTock {
497    #[inline]
498    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
499        match slice.load_bit() {
500            Ok(false) => Ok(Self::Tick),
501            Ok(true) => Ok(Self::Tock),
502            Err(e) => Err(e),
503        }
504    }
505}
506
507/// Account state hash update.
508#[derive(Debug, Clone, Copy, Eq, PartialEq, Store, Load)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
510#[tlb(tag = "#72")]
511pub struct HashUpdate {
512    /// Old account state hash.
513    pub old: HashBytes,
514    /// New account state hash.
515    pub new: HashBytes,
516}