stellar_base/operations/
invoke_host_function.rs

1use crate::crypto::MuxedAccount;
2use crate::error::{Error, Result};
3use crate::operations::Operation;
4use crate::xdr;
5
6/// Represents an operation invoking a Soroban host function.
7///
8/// Soroban smart contracts are accessed through host functions that are executed
9/// atomically within the context of the transaction. This operation carries:
10/// - The host function (one of:
11///     * InvokeContract
12///     * CreateContract
13///     * UploadContractWasm
14///     * CreateContractV2
15///   )
16/// - Authorization entries proving the required signatures / invocation authorizations
17///
18/// The XDR struct backing this is:
19/// ```text
20/// struct InvokeHostFunctionOp {
21///     HostFunction hostFunction;
22///     SorobanAuthorizationEntry auth<>;
23/// };
24/// ```
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct InvokeHostFunctionOperation {
27    source_account: Option<MuxedAccount>,
28    host_function: xdr::HostFunction,
29    auth: Vec<xdr::SorobanAuthorizationEntry>,
30}
31
32/// Builder for `InvokeHostFunctionOperation`.
33#[derive(Debug, Default)]
34pub struct InvokeHostFunctionOperationBuilder {
35    source_account: Option<MuxedAccount>,
36    host_function: Option<xdr::HostFunction>,
37    auth: Vec<xdr::SorobanAuthorizationEntry>,
38}
39
40impl InvokeHostFunctionOperation {
41    /// Source account (if any) overriding the transaction source.
42    pub fn source_account(&self) -> &Option<MuxedAccount> {
43        &self.source_account
44    }
45
46    /// Mutable reference to the source account.
47    pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
48        &mut self.source_account
49    }
50
51    /// The host function to invoke.
52    pub fn host_function(&self) -> &xdr::HostFunction {
53        &self.host_function
54    }
55
56    /// Mutable reference to the host function.
57    pub fn host_function_mut(&mut self) -> &mut xdr::HostFunction {
58        &mut self.host_function
59    }
60
61    /// Authorization entries.
62    pub fn auth(&self) -> &[xdr::SorobanAuthorizationEntry] {
63        &self.auth
64    }
65
66    /// Mutable authorization entries.
67    pub fn auth_mut(&mut self) -> &mut Vec<xdr::SorobanAuthorizationEntry> {
68        &mut self.auth
69    }
70
71    /// Returns the XDR operation body.
72    pub fn to_xdr_operation_body(&self) -> Result<xdr::OperationBody> {
73        let auth_xdr = self.auth.clone().try_into().map_err(|_| Error::XdrError)?;
74        let inner = xdr::InvokeHostFunctionOp {
75            host_function: self.host_function.clone(),
76            auth: auth_xdr,
77        };
78        Ok(xdr::OperationBody::InvokeHostFunction(inner))
79    }
80
81    /// Creates from the XDR operation body.
82    pub fn from_xdr_operation_body(
83        source_account: Option<MuxedAccount>,
84        x: &xdr::InvokeHostFunctionOp,
85    ) -> Result<InvokeHostFunctionOperation> {
86        let auth: Vec<xdr::SorobanAuthorizationEntry> = x.auth.iter().cloned().collect();
87        Ok(InvokeHostFunctionOperation {
88            source_account,
89            host_function: x.host_function.clone(),
90            auth,
91        })
92    }
93}
94
95impl InvokeHostFunctionOperationBuilder {
96    /// New builder.
97    pub fn new() -> Self {
98        Default::default()
99    }
100
101    /// Sets the source account.
102    pub fn with_source_account<S>(mut self, source: S) -> Self
103    where
104        S: Into<MuxedAccount>,
105    {
106        self.source_account = Some(source.into());
107        self
108    }
109
110    /// Sets the host function.
111    pub fn with_host_function(mut self, hf: xdr::HostFunction) -> Self {
112        self.host_function = Some(hf);
113        self
114    }
115
116    /// Replaces the authorization entries.
117    pub fn with_auth(mut self, auth: Vec<xdr::SorobanAuthorizationEntry>) -> Self {
118        self.auth = auth;
119        self
120    }
121
122    /// Adds a single authorization entry.
123    pub fn add_auth_entry(mut self, entry: xdr::SorobanAuthorizationEntry) -> Self {
124        self.auth.push(entry);
125        self
126    }
127
128    /// Convenience: host function InvokeContract.
129    pub fn with_invoke_contract(
130        mut self,
131        contract_address: xdr::ScAddress,
132        function_name: xdr::ScSymbol,
133        args: Vec<xdr::ScVal>,
134    ) -> Self {
135        let args_xdr = args.try_into().unwrap_or_else(|_| xdr::VecM::default());
136        let invoke_args = xdr::InvokeContractArgs {
137            contract_address,
138            function_name,
139            args: args_xdr,
140        };
141        self.host_function = Some(xdr::HostFunction::InvokeContract(invoke_args));
142        self
143    }
144
145    /// Convenience: host function UploadContractWasm.
146    pub fn with_upload_wasm(mut self, wasm: xdr::BytesM) -> Self {
147        self.host_function = Some(xdr::HostFunction::UploadContractWasm(wasm));
148        self
149    }
150
151    /// Convenience: host function CreateContract.
152    pub fn with_create_contract(
153        mut self,
154        contract_id_preimage: xdr::ContractIdPreimage,
155        executable: xdr::ContractExecutable,
156    ) -> Self {
157        let args = xdr::CreateContractArgs {
158            contract_id_preimage,
159            executable,
160        };
161        self.host_function = Some(xdr::HostFunction::CreateContract(args));
162        self
163    }
164
165    /// Convenience: host function CreateContractV2.
166    pub fn with_create_contract_v2(
167        mut self,
168        contract_id_preimage: xdr::ContractIdPreimage,
169        executable: xdr::ContractExecutable,
170        constructor_args: Vec<xdr::ScVal>,
171    ) -> Self {
172        let ctor_args_xdr = constructor_args
173            .try_into()
174            .unwrap_or_else(|_| xdr::VecM::default());
175        let args = xdr::CreateContractArgsV2 {
176            contract_id_preimage,
177            executable,
178            constructor_args: ctor_args_xdr,
179        };
180        self.host_function = Some(xdr::HostFunction::CreateContractV2(args));
181        self
182    }
183
184    /// Builds the operation.
185    pub fn build(self) -> Result<Operation> {
186        let host_function = self.host_function.ok_or_else(|| {
187            Error::InvalidOperation("missing host function for invoke host function".to_string())
188        })?;
189
190        // Validate number of auth entries vs some arbitrary large limit used when constructing VecM.
191        if self.auth.len() > 10_000 {
192            return Err(Error::InvalidOperation(
193                "too many authorization entries for invoke host function".to_string(),
194            ));
195        }
196
197        Ok(Operation::InvokeHostFunction(InvokeHostFunctionOperation {
198            source_account: self.source_account,
199            host_function,
200            auth: self.auth,
201        }))
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::operations::Operation;
209
210    use crate::xdr::{ContractId, Hash, ScAddress, ScSymbol, XDRDeserialize, XDRSerialize};
211
212    #[test]
213    fn test_invoke_host_function_invoke_contract_roundtrip() {
214        // Build a simple InvokeContract host function.
215        let contract_hash: [u8; 32] = [1u8; 32];
216        let addr = ScAddress::Contract(ContractId(Hash(contract_hash)));
217        // For simplicity, construct a symbol from a short ascii name. ScSymbol in current XDR is a
218        // length-limited opaque or string-like type; we rely on From<String> or TryFrom<Vec<u8>>.
219        let symbol: ScSymbol = "ping".to_string().try_into().unwrap();
220
221        let op = InvokeHostFunctionOperationBuilder::new()
222            .with_invoke_contract(addr, symbol, vec![])
223            .build()
224            .unwrap();
225
226        let encoded = op.xdr_base64().unwrap();
227        let decoded = Operation::from_xdr_base64(&encoded).unwrap();
228        assert_eq!(op, decoded);
229        if let Operation::InvokeHostFunction(inner) = decoded {
230            assert!(matches!(
231                inner.host_function(),
232                xdr::HostFunction::InvokeContract(_)
233            ));
234        } else {
235            panic!("expected invoke host function operation");
236        }
237    }
238}