everscale_types/models/transaction/
mod.rs

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