1use stellar_xdr::curr::{
7 AccountId, Asset, ContractExecutable, ContractIdPreimage, CreateContractArgs,
8 CreateContractArgsV2, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, Operation,
9 OperationBody, PaymentOp, ScAddress, ScSymbol, ScVal, SorobanAuthorizationEntry,
10 SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanCredentials, VecM,
11};
12
13use crate::error::SorobanHelperError;
14
15pub struct Operations;
21
22impl Operations {
23 pub fn upload_wasm(wasm_bytes: Vec<u8>) -> Result<Operation, SorobanHelperError> {
38 Ok(Operation {
39 source_account: None,
40 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
41 host_function: HostFunction::UploadContractWasm(wasm_bytes.try_into().map_err(
42 |e| {
43 SorobanHelperError::XdrEncodingFailed(format!(
44 "Failed to encode WASM bytes: {}",
45 e
46 ))
47 },
48 )?),
49 auth: VecM::default(),
50 }),
51 })
52 }
53
54 pub fn create_contract(
71 contract_id_preimage: ContractIdPreimage,
72 wasm_hash: Hash,
73 constructor_args: Option<Vec<ScVal>>,
74 ) -> Result<Operation, SorobanHelperError> {
75 match constructor_args {
76 Some(args) => {
77 Self::create_contract_with_constructor(contract_id_preimage, wasm_hash, args)
78 }
79 None => Self::create_contract_without_constructor(contract_id_preimage, wasm_hash),
80 }
81 }
82
83 fn create_contract_with_constructor(
100 contract_id_preimage: ContractIdPreimage,
101 wasm_hash: Hash,
102 constructor_args: Vec<ScVal>,
103 ) -> Result<Operation, SorobanHelperError> {
104 let args: VecM<ScVal, { u32::MAX }> = constructor_args.try_into().map_err(|e| {
105 SorobanHelperError::XdrEncodingFailed(format!(
106 "Failed to encode constructor args: {}",
107 e
108 ))
109 })?;
110
111 let create_args = CreateContractArgsV2 {
112 contract_id_preimage,
113 executable: ContractExecutable::Wasm(wasm_hash),
114 constructor_args: args,
115 };
116
117 let auth_entry = SorobanAuthorizationEntry {
118 credentials: SorobanCredentials::SourceAccount,
119 root_invocation: SorobanAuthorizedInvocation {
120 function: SorobanAuthorizedFunction::CreateContractV2HostFn(create_args.clone()),
121 sub_invocations: VecM::default(),
122 },
123 };
124
125 Ok(Operation {
126 source_account: None,
127 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
128 auth: vec![auth_entry].try_into().map_err(|e| {
129 SorobanHelperError::XdrEncodingFailed(format!(
130 "Failed to encode auth entries: {}",
131 e
132 ))
133 })?,
134 host_function: HostFunction::CreateContractV2(create_args),
135 }),
136 })
137 }
138
139 fn create_contract_without_constructor(
155 contract_id_preimage: ContractIdPreimage,
156 wasm_hash: Hash,
157 ) -> Result<Operation, SorobanHelperError> {
158 let create_args = CreateContractArgs {
159 contract_id_preimage,
160 executable: ContractExecutable::Wasm(wasm_hash),
161 };
162
163 let auth_entry = SorobanAuthorizationEntry {
164 credentials: SorobanCredentials::SourceAccount,
165 root_invocation: SorobanAuthorizedInvocation {
166 function: SorobanAuthorizedFunction::CreateContractHostFn(create_args.clone()),
167 sub_invocations: VecM::default(),
168 },
169 };
170
171 Ok(Operation {
172 source_account: None,
173 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
174 auth: vec![auth_entry].try_into().map_err(|e| {
175 SorobanHelperError::XdrEncodingFailed(format!(
176 "Failed to encode auth entries: {}",
177 e
178 ))
179 })?,
180 host_function: HostFunction::CreateContract(create_args),
181 }),
182 })
183 }
184
185 pub fn invoke_contract(
203 contract_id: &stellar_strkey::Contract,
204 function_name: &str,
205 args: Vec<ScVal>,
206 ) -> Result<Operation, SorobanHelperError> {
207 let invoke_contract_args = InvokeContractArgs {
208 contract_address: ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(
209 contract_id.0,
210 ))),
211 function_name: ScSymbol(function_name.try_into().map_err(|e| {
212 SorobanHelperError::InvalidArgument(format!("Invalid function name: {}", e))
213 })?),
214 args: args.try_into().map_err(|e| {
215 SorobanHelperError::XdrEncodingFailed(format!("Failed to encode arguments: {}", e))
216 })?,
217 };
218
219 Ok(Operation {
220 source_account: None,
221 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
222 host_function: HostFunction::InvokeContract(invoke_contract_args),
223 auth: VecM::default(),
224 }),
225 })
226 }
227
228 pub fn send_payment(
229 to: AccountId,
230 amount: i64,
231 asset: Asset,
232 ) -> Result<Operation, SorobanHelperError> {
233 Ok(Operation {
234 source_account: None,
235 body: OperationBody::Payment(PaymentOp {
236 amount,
237 destination: to.into(),
238 asset,
239 }),
240 })
241 }
242}
243
244#[cfg(test)]
245mod test {
246 use super::*;
247 use stellar_xdr::curr::{ContractIdPreimageFromAddress, PublicKey, ScVal};
248
249 #[test]
250 fn test_upload_wasm() {
251 let wasm_bytes = vec![0, 1, 2, 3, 4, 5];
252 let operation = Operations::upload_wasm(wasm_bytes.clone()).unwrap();
253
254 assert!(matches!(
255 operation.body,
256 OperationBody::InvokeHostFunction(_)
257 ));
258 if let OperationBody::InvokeHostFunction(op) = operation.body {
259 assert!(matches!(
260 op.host_function,
261 HostFunction::UploadContractWasm(_)
262 ));
263 assert_eq!(op.auth.len(), 0);
264 }
265 }
266
267 #[test]
268 fn test_create_contract_without_args() {
269 let account_id =
270 stellar_xdr::curr::AccountId(PublicKey::PublicKeyTypeEd25519([0; 32].into()));
271 let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
272 address: ScAddress::Account(account_id),
273 salt: [1; 32].into(),
274 });
275
276 let wasm_hash = Hash([2; 32]);
277 let operation =
278 Operations::create_contract(contract_id_preimage.clone(), wasm_hash.clone(), None)
279 .unwrap();
280
281 assert!(matches!(
282 operation.body,
283 OperationBody::InvokeHostFunction(_)
284 ));
285 if let OperationBody::InvokeHostFunction(op) = operation.body {
286 assert!(matches!(op.host_function, HostFunction::CreateContract(_)));
287 assert_eq!(op.auth.len(), 1);
288
289 if let HostFunction::CreateContract(args) = op.host_function {
290 assert_eq!(args.contract_id_preimage, contract_id_preimage);
291 assert!(matches!(args.executable, ContractExecutable::Wasm(h) if h == wasm_hash));
292 }
293 }
294 }
295
296 #[test]
297 fn test_create_contract_with_args() {
298 let account_id =
299 stellar_xdr::curr::AccountId(PublicKey::PublicKeyTypeEd25519([0; 32].into()));
300 let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
301 address: ScAddress::Account(account_id),
302 salt: [1; 32].into(),
303 });
304
305 let wasm_hash = Hash([2; 32]);
306 let constructor_args = vec![ScVal::I32(42), ScVal::Bool(true)];
307 let operation = Operations::create_contract(
308 contract_id_preimage.clone(),
309 wasm_hash.clone(),
310 Some(constructor_args.clone()),
311 )
312 .unwrap();
313
314 assert!(matches!(
315 operation.body,
316 OperationBody::InvokeHostFunction(_)
317 ));
318 if let OperationBody::InvokeHostFunction(op) = operation.body {
319 assert!(matches!(
320 op.host_function,
321 HostFunction::CreateContractV2(_)
322 ));
323 assert_eq!(op.auth.len(), 1);
324
325 if let HostFunction::CreateContractV2(args) = op.host_function {
326 assert_eq!(args.contract_id_preimage, contract_id_preimage);
327 assert!(matches!(args.executable, ContractExecutable::Wasm(h) if h == wasm_hash));
328
329 assert_eq!(args.constructor_args.len(), 2);
330 assert!(matches!(args.constructor_args[0], ScVal::I32(42)));
331 assert!(matches!(args.constructor_args[1], ScVal::Bool(true)));
332 }
333 }
334 }
335
336 #[test]
337 fn test_invoke_contract() {
338 let contract_bytes = [3; 32];
339 let contract_id = stellar_strkey::Contract(contract_bytes);
340
341 let function_name = "test_function";
342 let args = vec![ScVal::I32(42), ScVal::Bool(true)];
343 let operation =
344 Operations::invoke_contract(&contract_id, function_name, args.clone()).unwrap();
345
346 assert!(matches!(
347 operation.body,
348 OperationBody::InvokeHostFunction(_)
349 ));
350 if let OperationBody::InvokeHostFunction(op) = operation.body {
351 assert!(matches!(op.host_function, HostFunction::InvokeContract(_)));
352 assert_eq!(op.auth.len(), 0);
353
354 if let HostFunction::InvokeContract(args) = op.host_function {
355 assert!(
356 matches!(args.contract_address, ScAddress::Contract(stellar_xdr::curr::ContractId(hash)) if hash.0 == contract_bytes)
357 );
358 assert_eq!(args.function_name.0.as_slice(), function_name.as_bytes());
359
360 assert_eq!(args.args.len(), 2);
361 assert!(matches!(args.args[0], ScVal::I32(42)));
362 assert!(matches!(args.args[1], ScVal::Bool(true)));
363 }
364 }
365 }
366
367 #[test]
368 fn test_invoke_contract_invalid_function_name() {
369 let contract_bytes = [3; 32];
370 let contract_id = stellar_strkey::Contract(contract_bytes);
371
372 let invalid_function_name = "a".repeat(33); let args = vec![];
374
375 let result = Operations::invoke_contract(&contract_id, &invalid_function_name, args);
376
377 assert!(result.is_err());
378 assert!(matches!(
379 result,
380 Err(SorobanHelperError::InvalidArgument(_))
381 ));
382 }
383}