stellar_base/operations/
extend_footprint_ttl.rs

1use crate::crypto::MuxedAccount;
2use crate::error::{Error, Result};
3use crate::operations::Operation;
4use crate::xdr;
5
6/// Represents an operation to extend the footprint TTL for Soroban (smart contract) related
7/// ledger entries.
8///
9/// Soroban introduces temporary ledger entries with a TTL (time to live) that can be extended.
10/// This operation sets the TTL of all entries in the transaction's footprint to at least
11/// `extend_to` (a ledger sequence number). If an entry already exceeds this value, it is
12/// unaffected.
13///
14/// NOTE: The `ext` field in `ExtendFootprintTtlOp` is currently always the empty
15/// `ExtensionPoint::V0`.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ExtendFootprintTtlOperation {
18    source_account: Option<MuxedAccount>,
19    extend_to: u32,
20}
21
22/// Builder for `ExtendFootprintTtlOperation`.
23#[derive(Debug, Default)]
24pub struct ExtendFootprintTtlOperationBuilder {
25    source_account: Option<MuxedAccount>,
26    extend_to: Option<u32>,
27}
28
29impl ExtendFootprintTtlOperation {
30    /// Retrieves the operation source account.
31    pub fn source_account(&self) -> &Option<MuxedAccount> {
32        &self.source_account
33    }
34
35    /// Retrieves a mutable reference to the operation source account.
36    pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
37        &mut self.source_account
38    }
39
40    /// Retrieves the target ledger sequence number to extend TTLs to.
41    pub fn extend_to(&self) -> &u32 {
42        &self.extend_to
43    }
44
45    /// Retrieves a mutable reference to the target ledger sequence number.
46    pub fn extend_to_mut(&mut self) -> &mut u32 {
47        &mut self.extend_to
48    }
49
50    /// Returns the XDR operation body.
51    pub fn to_xdr_operation_body(&self) -> Result<xdr::OperationBody> {
52        let inner = xdr::ExtendFootprintTtlOp {
53            ext: xdr::ExtensionPoint::V0,
54            extend_to: self.extend_to,
55        };
56        Ok(xdr::OperationBody::ExtendFootprintTtl(inner))
57    }
58
59    /// Creates from the XDR operation body.
60    pub fn from_xdr_operation_body(
61        source_account: Option<MuxedAccount>,
62        x: &xdr::ExtendFootprintTtlOp,
63    ) -> Result<ExtendFootprintTtlOperation> {
64        Ok(ExtendFootprintTtlOperation {
65            source_account,
66            extend_to: x.extend_to,
67        })
68    }
69}
70
71impl ExtendFootprintTtlOperationBuilder {
72    pub fn new() -> ExtendFootprintTtlOperationBuilder {
73        Default::default()
74    }
75
76    /// Sets the source account for the operation.
77    pub fn with_source_account<S>(mut self, source: S) -> ExtendFootprintTtlOperationBuilder
78    where
79        S: Into<MuxedAccount>,
80    {
81        self.source_account = Some(source.into());
82        self
83    }
84
85    /// Sets the ledger sequence number to extend TTLs to.
86    pub fn with_extend_to(mut self, extend_to: u32) -> ExtendFootprintTtlOperationBuilder {
87        self.extend_to = Some(extend_to);
88        self
89    }
90
91    /// Builds the `Operation`.
92    pub fn build(self) -> Result<Operation> {
93        let extend_to = self.extend_to.ok_or_else(|| {
94            Error::InvalidOperation("missing extend footprint ttl extend to".to_string())
95        })?;
96
97        // A TTL of 0 does not make sense (ledger sequence numbers start from 1 and
98        // a value of 0 would never extend anything). Enforce > 0 to catch obvious mistakes.
99        if extend_to == 0 {
100            return Err(Error::InvalidOperation(
101                "extend footprint ttl extend_to must be greater than zero".to_string(),
102            ));
103        }
104
105        Ok(Operation::ExtendFootprintTtl(ExtendFootprintTtlOperation {
106            source_account: self.source_account,
107            extend_to,
108        }))
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::operations::tests::*;
116    use crate::operations::Operation;
117    use crate::xdr::{XDRDeserialize, XDRSerialize};
118
119    #[test]
120    fn test_extend_footprint_ttl_roundtrip() {
121        let op = ExtendFootprintTtlOperationBuilder::new()
122            .with_extend_to(12345)
123            .build()
124            .unwrap();
125
126        let encoded = op.xdr_base64().unwrap();
127        let decoded = Operation::from_xdr_base64(&encoded).unwrap();
128        assert_eq!(op, decoded);
129        assert!(decoded.source_account().is_none());
130        if let Operation::ExtendFootprintTtl(inner) = &decoded {
131            assert_eq!(*inner.extend_to(), 12345);
132        } else {
133            panic!("expected ExtendFootprintTtl operation");
134        }
135    }
136
137    #[test]
138    fn test_extend_footprint_ttl_with_source_account() {
139        let source = keypair0().public_key();
140        let op = ExtendFootprintTtlOperationBuilder::new()
141            .with_source_account(source)
142            .with_extend_to(999_999)
143            .build()
144            .unwrap();
145
146        let encoded = op.xdr_base64().unwrap();
147        let decoded = Operation::from_xdr_base64(&encoded).unwrap();
148        assert_eq!(op, decoded);
149        let inner = match &decoded {
150            Operation::ExtendFootprintTtl(op) => op,
151            _ => panic!("expected ExtendFootprintTtl operation"),
152        };
153        assert_eq!(*inner.extend_to(), 999_999);
154        assert!(inner.source_account().is_some());
155    }
156}