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(Hash(contract_id.0)),
209 function_name: ScSymbol(function_name.try_into().map_err(|e| {
210 SorobanHelperError::InvalidArgument(format!("Invalid function name: {}", e))
211 })?),
212 args: args.try_into().map_err(|e| {
213 SorobanHelperError::XdrEncodingFailed(format!("Failed to encode arguments: {}", e))
214 })?,
215 };
216
217 Ok(Operation {
218 source_account: None,
219 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
220 host_function: HostFunction::InvokeContract(invoke_contract_args),
221 auth: VecM::default(),
222 }),
223 })
224 }
225
226 pub fn send_payment(
227 to: AccountId,
228 amount: i64,
229 asset: Asset,
230 ) -> Result<Operation, SorobanHelperError> {
231 Ok(Operation {
232 source_account: None,
233 body: OperationBody::Payment(PaymentOp {
234 amount,
235 destination: to.into(),
236 asset,
237 }),
238 })
239 }
240}
241
242#[cfg(test)]
243mod test {
244 use super::*;
245 use stellar_xdr::curr::{ContractIdPreimageFromAddress, PublicKey, ScVal};
246
247 #[test]
248 fn test_upload_wasm() {
249 let wasm_bytes = vec![0, 1, 2, 3, 4, 5];
250 let operation = Operations::upload_wasm(wasm_bytes.clone()).unwrap();
251
252 assert!(matches!(
253 operation.body,
254 OperationBody::InvokeHostFunction(_)
255 ));
256 if let OperationBody::InvokeHostFunction(op) = operation.body {
257 assert!(matches!(
258 op.host_function,
259 HostFunction::UploadContractWasm(_)
260 ));
261 assert_eq!(op.auth.len(), 0);
262 }
263 }
264
265 #[test]
266 fn test_create_contract_without_args() {
267 let account_id =
268 stellar_xdr::curr::AccountId(PublicKey::PublicKeyTypeEd25519([0; 32].into()));
269 let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
270 address: ScAddress::Account(account_id),
271 salt: [1; 32].into(),
272 });
273
274 let wasm_hash = Hash([2; 32]);
275 let operation =
276 Operations::create_contract(contract_id_preimage.clone(), wasm_hash.clone(), None)
277 .unwrap();
278
279 assert!(matches!(
280 operation.body,
281 OperationBody::InvokeHostFunction(_)
282 ));
283 if let OperationBody::InvokeHostFunction(op) = operation.body {
284 assert!(matches!(op.host_function, HostFunction::CreateContract(_)));
285 assert_eq!(op.auth.len(), 1);
286
287 if let HostFunction::CreateContract(args) = op.host_function {
288 assert_eq!(args.contract_id_preimage, contract_id_preimage);
289 assert!(matches!(args.executable, ContractExecutable::Wasm(h) if h == wasm_hash));
290 }
291 }
292 }
293
294 #[test]
295 fn test_create_contract_with_args() {
296 let account_id =
297 stellar_xdr::curr::AccountId(PublicKey::PublicKeyTypeEd25519([0; 32].into()));
298 let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
299 address: ScAddress::Account(account_id),
300 salt: [1; 32].into(),
301 });
302
303 let wasm_hash = Hash([2; 32]);
304 let constructor_args = vec![ScVal::I32(42), ScVal::Bool(true)];
305 let operation = Operations::create_contract(
306 contract_id_preimage.clone(),
307 wasm_hash.clone(),
308 Some(constructor_args.clone()),
309 )
310 .unwrap();
311
312 assert!(matches!(
313 operation.body,
314 OperationBody::InvokeHostFunction(_)
315 ));
316 if let OperationBody::InvokeHostFunction(op) = operation.body {
317 assert!(matches!(
318 op.host_function,
319 HostFunction::CreateContractV2(_)
320 ));
321 assert_eq!(op.auth.len(), 1);
322
323 if let HostFunction::CreateContractV2(args) = op.host_function {
324 assert_eq!(args.contract_id_preimage, contract_id_preimage);
325 assert!(matches!(args.executable, ContractExecutable::Wasm(h) if h == wasm_hash));
326
327 assert_eq!(args.constructor_args.len(), 2);
328 assert!(matches!(args.constructor_args[0], ScVal::I32(42)));
329 assert!(matches!(args.constructor_args[1], ScVal::Bool(true)));
330 }
331 }
332 }
333
334 #[test]
335 fn test_invoke_contract() {
336 let contract_bytes = [3; 32];
337 let contract_id = stellar_strkey::Contract(contract_bytes);
338
339 let function_name = "test_function";
340 let args = vec![ScVal::I32(42), ScVal::Bool(true)];
341 let operation =
342 Operations::invoke_contract(&contract_id, function_name, args.clone()).unwrap();
343
344 assert!(matches!(
345 operation.body,
346 OperationBody::InvokeHostFunction(_)
347 ));
348 if let OperationBody::InvokeHostFunction(op) = operation.body {
349 assert!(matches!(op.host_function, HostFunction::InvokeContract(_)));
350 assert_eq!(op.auth.len(), 0);
351
352 if let HostFunction::InvokeContract(args) = op.host_function {
353 assert!(
354 matches!(args.contract_address, ScAddress::Contract(hash) if hash.0 == contract_bytes)
355 );
356 assert_eq!(args.function_name.0.as_slice(), function_name.as_bytes());
357
358 assert_eq!(args.args.len(), 2);
359 assert!(matches!(args.args[0], ScVal::I32(42)));
360 assert!(matches!(args.args[1], ScVal::Bool(true)));
361 }
362 }
363 }
364
365 #[test]
366 fn test_invoke_contract_invalid_function_name() {
367 let contract_bytes = [3; 32];
368 let contract_id = stellar_strkey::Contract(contract_bytes);
369
370 let invalid_function_name = "a".repeat(33); let args = vec![];
372
373 let result = Operations::invoke_contract(&contract_id, &invalid_function_name, args);
374
375 assert!(result.is_err());
376 assert!(matches!(
377 result,
378 Err(SorobanHelperError::InvalidArgument(_))
379 ));
380 }
381}