hiero_sdk/contract/contract_call_query.rs
1// SPDX-License-Identifier: Apache-2.0
2
3use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::smart_contract_service_client::SmartContractServiceClient;
5use tonic::transport::Channel;
6
7use crate::ledger_id::RefLedgerId;
8use crate::query::{
9 AnyQueryData,
10 QueryExecute,
11 ToQueryProtobuf,
12};
13use crate::{
14 AccountId,
15 BoxGrpcFuture,
16 ContractFunctionParameters,
17 ContractFunctionResult,
18 ContractId,
19 Error,
20 Query,
21 ToProtobuf,
22 ValidateChecksums,
23};
24
25/// Call a function of the given smart contract instance.
26/// It will consume the entire given amount of gas.
27///
28/// This is performed locally on the particular node that the client is communicating with.
29/// It cannot change the state of the contract instance (and so, cannot spend
30/// anything from the instance's cryptocurrency account).
31///
32pub type ContractCallQuery = Query<ContractCallQueryData>;
33
34#[derive(Default, Debug, Clone)]
35pub struct ContractCallQueryData {
36 /// The contract instance to call.
37 contract_id: Option<ContractId>,
38
39 /// The amount of gas to use for the call.
40 gas: u64,
41
42 /// The function parameters as their raw bytes.
43 function_parameters: Vec<u8>,
44
45 /// The sender for this transaction.
46 sender_account_id: Option<AccountId>,
47}
48
49impl ContractCallQuery {
50 /// Gets the contract instance to call.
51 #[must_use]
52 pub fn get_contract_id(&self) -> Option<ContractId> {
53 self.data.contract_id
54 }
55
56 /// Sets the contract to make a static call against.
57 pub fn contract_id(&mut self, contract_id: ContractId) -> &mut Self {
58 self.data.contract_id = Some(contract_id);
59 self
60 }
61
62 /// Gets the amount of gas to use for the call.
63 #[must_use]
64 pub fn get_gas(&self) -> u64 {
65 self.data.gas
66 }
67
68 /// Sets the amount of gas to use for the call.
69 pub fn gas(&mut self, gas: u64) -> &mut Self {
70 self.data.gas = gas;
71 self
72 }
73
74 /// Gets the function parameters as their raw bytes.
75 #[must_use]
76 pub fn get_contract_parameters(&self) -> &[u8] {
77 self.data.function_parameters.as_ref()
78 }
79
80 /// Sets the function parameters as their raw bytes.
81 pub fn function_parameters(&mut self, data: Vec<u8>) -> &mut Self {
82 self.data.function_parameters = data;
83 self
84 }
85
86 /// Sets the function with no parameters.
87 pub fn function(&mut self, name: &str) -> &mut Self {
88 self.function_with_parameters(name, &ContractFunctionParameters::new())
89 }
90
91 /// Sets the function with parameters.
92 pub fn function_with_parameters(
93 &mut self,
94 name: &str,
95 parameters: &ContractFunctionParameters,
96 ) -> &mut Self {
97 self.function_parameters(parameters.to_bytes(Some(name)))
98 }
99
100 /// Gets the sender for this transaction.
101 #[must_use]
102 pub fn get_sender_account_id(&self) -> Option<AccountId> {
103 self.data.sender_account_id
104 }
105
106 /// Sets the sender for this transaction.
107 pub fn sender_account_id(&mut self, sender_account_id: AccountId) -> &mut Self {
108 self.data.sender_account_id = Some(sender_account_id);
109 self
110 }
111}
112
113impl From<ContractCallQueryData> for AnyQueryData {
114 #[inline]
115 fn from(data: ContractCallQueryData) -> Self {
116 Self::ContractCall(data)
117 }
118}
119
120impl ToQueryProtobuf for ContractCallQueryData {
121 fn to_query_protobuf(&self, header: services::QueryHeader) -> services::Query {
122 let contract_id = self.contract_id.to_protobuf();
123 let sender_id = self.sender_account_id.to_protobuf();
124
125 services::Query {
126 query: Some(services::query::Query::ContractCallLocal(
127 #[allow(deprecated)]
128 services::ContractCallLocalQuery {
129 contract_id,
130 gas: self.gas as i64,
131 function_parameters: self.function_parameters.clone(),
132 max_result_size: 0,
133 header: Some(header),
134 sender_id,
135 },
136 )),
137 }
138 }
139}
140
141impl QueryExecute for ContractCallQueryData {
142 type Response = ContractFunctionResult;
143
144 fn execute(
145 &self,
146 channel: Channel,
147 request: services::Query,
148 ) -> BoxGrpcFuture<'_, services::Response> {
149 Box::pin(async {
150 SmartContractServiceClient::new(channel).contract_call_local_method(request).await
151 })
152 }
153}
154
155impl ValidateChecksums for ContractCallQueryData {
156 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
157 self.contract_id.validate_checksums(ledger_id)?;
158 self.sender_account_id.validate_checksums(ledger_id)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use expect_test::expect;
165 use hiero_sdk_proto::services;
166
167 use crate::query::ToQueryProtobuf;
168 use crate::{
169 AccountId,
170 ContractCallQuery,
171 ContractFunctionParameters,
172 ContractId,
173 Hbar,
174 };
175
176 fn make_query() -> ContractCallQuery {
177 let mut query = ContractCallQuery::new();
178
179 query
180 .contract_id(crate::ContractId::new(0, 0, 5005))
181 .gas(1541)
182 .sender_account_id("1.2.3".parse().unwrap())
183 .max_payment_amount(Hbar::from_tinybars(100_000));
184
185 query
186 }
187
188 #[test]
189 fn serialize() {
190 expect![[r#"
191 Query {
192 query: Some(
193 ContractCallLocal(
194 ContractCallLocalQuery {
195 header: Some(
196 QueryHeader {
197 payment: None,
198 response_type: AnswerOnly,
199 },
200 ),
201 contract_id: Some(
202 ContractId {
203 shard_num: 0,
204 realm_num: 0,
205 contract: Some(
206 ContractNum(
207 5005,
208 ),
209 ),
210 },
211 ),
212 gas: 1541,
213 function_parameters: [
214 18,
215 74,
216 131,
217 250,
218 0,
219 0,
220 0,
221 0,
222 0,
223 0,
224 0,
225 0,
226 0,
227 0,
228 0,
229 0,
230 0,
231 0,
232 0,
233 0,
234 0,
235 0,
236 0,
237 0,
238 0,
239 0,
240 0,
241 0,
242 0,
243 0,
244 0,
245 0,
246 0,
247 0,
248 0,
249 64,
250 0,
251 0,
252 0,
253 0,
254 0,
255 0,
256 0,
257 0,
258 0,
259 0,
260 0,
261 0,
262 0,
263 0,
264 0,
265 0,
266 0,
267 0,
268 0,
269 0,
270 0,
271 0,
272 0,
273 0,
274 0,
275 0,
276 0,
277 0,
278 0,
279 0,
280 0,
281 128,
282 0,
283 0,
284 0,
285 0,
286 0,
287 0,
288 0,
289 0,
290 0,
291 0,
292 0,
293 0,
294 0,
295 0,
296 0,
297 0,
298 0,
299 0,
300 0,
301 0,
302 0,
303 0,
304 0,
305 0,
306 0,
307 0,
308 0,
309 0,
310 0,
311 0,
312 0,
313 5,
314 72,
315 101,
316 108,
317 108,
318 111,
319 0,
320 0,
321 0,
322 0,
323 0,
324 0,
325 0,
326 0,
327 0,
328 0,
329 0,
330 0,
331 0,
332 0,
333 0,
334 0,
335 0,
336 0,
337 0,
338 0,
339 0,
340 0,
341 0,
342 0,
343 0,
344 0,
345 0,
346 0,
347 0,
348 0,
349 0,
350 0,
351 0,
352 0,
353 0,
354 0,
355 0,
356 0,
357 0,
358 0,
359 0,
360 0,
361 0,
362 0,
363 0,
364 0,
365 0,
366 0,
367 0,
368 0,
369 0,
370 0,
371 0,
372 0,
373 0,
374 0,
375 0,
376 0,
377 6,
378 119,
379 111,
380 114,
381 108,
382 100,
383 33,
384 0,
385 0,
386 0,
387 0,
388 0,
389 0,
390 0,
391 0,
392 0,
393 0,
394 0,
395 0,
396 0,
397 0,
398 0,
399 0,
400 0,
401 0,
402 0,
403 0,
404 0,
405 0,
406 0,
407 0,
408 0,
409 0,
410 ],
411 max_result_size: 0,
412 sender_id: Some(
413 AccountId {
414 shard_num: 1,
415 realm_num: 2,
416 account: Some(
417 AccountNum(
418 3,
419 ),
420 ),
421 },
422 ),
423 },
424 ),
425 ),
426 }
427 "#]]
428 .assert_debug_eq(
429 &make_query()
430 .function_with_parameters(
431 "foo",
432 ContractFunctionParameters::new().add_string("Hello").add_string("world!"),
433 )
434 .data
435 .to_query_protobuf(services::QueryHeader::default()),
436 );
437 }
438
439 #[test]
440 fn function_parameters() {
441 expect![[r#"
442 Query {
443 query: Some(
444 ContractCallLocal(
445 ContractCallLocalQuery {
446 header: Some(
447 QueryHeader {
448 payment: None,
449 response_type: AnswerOnly,
450 },
451 ),
452 contract_id: Some(
453 ContractId {
454 shard_num: 0,
455 realm_num: 0,
456 contract: Some(
457 ContractNum(
458 5005,
459 ),
460 ),
461 },
462 ),
463 gas: 1541,
464 function_parameters: [
465 0,
466 0,
467 0,
468 0,
469 0,
470 0,
471 0,
472 0,
473 0,
474 0,
475 0,
476 0,
477 0,
478 0,
479 0,
480 0,
481 0,
482 0,
483 0,
484 0,
485 0,
486 0,
487 0,
488 0,
489 0,
490 0,
491 0,
492 0,
493 0,
494 0,
495 0,
496 64,
497 0,
498 0,
499 0,
500 0,
501 0,
502 0,
503 0,
504 0,
505 0,
506 0,
507 0,
508 0,
509 0,
510 0,
511 0,
512 0,
513 0,
514 0,
515 0,
516 0,
517 0,
518 0,
519 0,
520 0,
521 0,
522 0,
523 0,
524 0,
525 0,
526 0,
527 0,
528 128,
529 0,
530 0,
531 0,
532 0,
533 0,
534 0,
535 0,
536 0,
537 0,
538 0,
539 0,
540 0,
541 0,
542 0,
543 0,
544 0,
545 0,
546 0,
547 0,
548 0,
549 0,
550 0,
551 0,
552 0,
553 0,
554 0,
555 0,
556 0,
557 0,
558 0,
559 0,
560 5,
561 72,
562 101,
563 108,
564 108,
565 111,
566 0,
567 0,
568 0,
569 0,
570 0,
571 0,
572 0,
573 0,
574 0,
575 0,
576 0,
577 0,
578 0,
579 0,
580 0,
581 0,
582 0,
583 0,
584 0,
585 0,
586 0,
587 0,
588 0,
589 0,
590 0,
591 0,
592 0,
593 0,
594 0,
595 0,
596 0,
597 0,
598 0,
599 0,
600 0,
601 0,
602 0,
603 0,
604 0,
605 0,
606 0,
607 0,
608 0,
609 0,
610 0,
611 0,
612 0,
613 0,
614 0,
615 0,
616 0,
617 0,
618 0,
619 0,
620 0,
621 0,
622 0,
623 0,
624 6,
625 119,
626 111,
627 114,
628 108,
629 100,
630 33,
631 0,
632 0,
633 0,
634 0,
635 0,
636 0,
637 0,
638 0,
639 0,
640 0,
641 0,
642 0,
643 0,
644 0,
645 0,
646 0,
647 0,
648 0,
649 0,
650 0,
651 0,
652 0,
653 0,
654 0,
655 0,
656 0,
657 ],
658 max_result_size: 0,
659 sender_id: Some(
660 AccountId {
661 shard_num: 1,
662 realm_num: 2,
663 account: Some(
664 AccountNum(
665 3,
666 ),
667 ),
668 },
669 ),
670 },
671 ),
672 ),
673 }
674 "#]]
675 .assert_debug_eq(
676 &make_query()
677 .function_parameters(
678 ContractFunctionParameters::new()
679 .add_string("Hello")
680 .add_string("world!")
681 .to_bytes(None),
682 )
683 .data
684 .to_query_protobuf(services::QueryHeader::default()),
685 );
686 }
687
688 #[test]
689 fn get_set_contract_id() {
690 let mut query = ContractCallQuery::new();
691 query.contract_id(ContractId::new(0, 0, 5005));
692
693 assert_eq!(query.get_contract_id(), Some(ContractId::new(0, 0, 5005)));
694 }
695
696 #[test]
697 fn get_set_gas() {
698 let mut query = ContractCallQuery::new();
699 query.gas(1541);
700
701 assert_eq!(query.get_gas(), 1541);
702 }
703
704 #[test]
705 fn get_set_contract_parameters() {
706 const BYTES: [u8; 6] = [0x0a, 0x0b, 0x0c, 0x01, 0x02, 0x03];
707 let mut query = ContractCallQuery::new();
708 query.function_parameters(Vec::from(BYTES));
709
710 assert_eq!(query.get_contract_parameters(), &BYTES);
711 }
712
713 #[test]
714 fn get_set_sender_account_id() {
715 let mut query = ContractCallQuery::new();
716 query.sender_account_id(AccountId::new(1, 2, 3));
717
718 assert_eq!(query.get_sender_account_id(), Some(AccountId::new(1, 2, 3)));
719 }
720}