1use hedera_proto::services;
4use hedera_proto::services::smart_contract_service_client::SmartContractServiceClient;
5use time::{
6 Duration,
7 OffsetDateTime,
8};
9use tonic::transport::Channel;
10
11use crate::ledger_id::RefLedgerId;
12use crate::protobuf::FromProtobuf;
13use crate::staked_id::StakedId;
14use crate::transaction::{
15 AnyTransactionData,
16 ChunkInfo,
17 ToSchedulableTransactionDataProtobuf,
18 ToTransactionDataProtobuf,
19 TransactionData,
20 TransactionExecute,
21};
22use crate::{
23 AccountId,
24 BoxGrpcFuture,
25 ContractId,
26 Error,
27 Key,
28 ToProtobuf,
29 Transaction,
30 ValidateChecksums,
31};
32
33pub type ContractUpdateTransaction = Transaction<ContractUpdateTransactionData>;
35
36#[derive(Debug, Default, Clone)]
37pub struct ContractUpdateTransactionData {
38 contract_id: Option<ContractId>,
39
40 expiration_time: Option<OffsetDateTime>,
41
42 admin_key: Option<Key>,
43
44 auto_renew_period: Option<Duration>,
45
46 contract_memo: Option<String>,
47
48 max_automatic_token_associations: Option<i32>,
49
50 auto_renew_account_id: Option<AccountId>,
51
52 proxy_account_id: Option<AccountId>,
53
54 staked_id: Option<StakedId>,
56
57 decline_staking_reward: Option<bool>,
58}
59
60impl ContractUpdateTransaction {
61 #[must_use]
63 pub fn get_contract_id(&self) -> Option<ContractId> {
64 self.data().contract_id
65 }
66
67 pub fn contract_id(&mut self, contract_id: ContractId) -> &mut Self {
69 self.data_mut().contract_id = Some(contract_id);
70 self
71 }
72
73 #[must_use]
75 pub fn get_admin_key(&self) -> Option<&Key> {
76 self.data().admin_key.as_ref()
77 }
78
79 pub fn admin_key(&mut self, key: impl Into<Key>) -> &mut Self {
81 self.data_mut().admin_key = Some(key.into());
82 self
83 }
84
85 #[must_use]
87 pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
88 self.data().expiration_time
89 }
90
91 pub fn expiration_time(&mut self, at: OffsetDateTime) -> &mut Self {
93 self.data_mut().expiration_time = Some(at);
94 self
95 }
96
97 #[must_use]
99 pub fn get_auto_renew_period(&self) -> Option<Duration> {
100 self.data().auto_renew_period
101 }
102
103 pub fn auto_renew_period(&mut self, period: Duration) -> &mut Self {
105 self.data_mut().auto_renew_period = Some(period);
106 self
107 }
108
109 #[must_use]
111 pub fn get_contract_memo(&self) -> Option<&str> {
112 self.data().contract_memo.as_deref()
113 }
114
115 pub fn contract_memo(&mut self, memo: impl Into<String>) -> &mut Self {
117 self.data_mut().contract_memo = Some(memo.into());
118 self
119 }
120
121 #[must_use]
123 pub fn get_max_automatic_token_associations(&self) -> Option<i32> {
124 self.data().max_automatic_token_associations
125 }
126
127 pub fn max_automatic_token_associations(&mut self, max: i32) -> &mut Self {
129 self.data_mut().max_automatic_token_associations = Some(max);
130 self
131 }
132
133 #[must_use]
136 pub fn get_auto_renew_account_id(&self) -> Option<AccountId> {
137 self.data().auto_renew_account_id
138 }
139
140 pub fn auto_renew_account_id(&mut self, account_id: AccountId) -> &mut Self {
143 self.data_mut().auto_renew_account_id = Some(account_id);
144 self
145 }
146
147 #[must_use]
149 pub fn get_proxy_account_id(&self) -> Option<AccountId> {
150 self.data().proxy_account_id
151 }
152
153 pub fn proxy_account_id(&mut self, id: AccountId) -> &mut Self {
155 self.data_mut().proxy_account_id = Some(id);
156 self
157 }
158
159 #[must_use]
161 pub fn get_staked_account_id(&self) -> Option<AccountId> {
162 self.data().staked_id.and_then(StakedId::to_account_id)
163 }
164
165 pub fn staked_account_id(&mut self, id: AccountId) -> &mut Self {
168 self.data_mut().staked_id = Some(id.into());
169 self
170 }
171
172 #[must_use]
174 pub fn get_staked_node_id(&self) -> Option<u64> {
175 self.data().staked_id.and_then(StakedId::to_node_id)
176 }
177
178 pub fn staked_node_id(&mut self, id: u64) -> &mut Self {
181 self.data_mut().staked_id = Some(id.into());
182 self
183 }
184
185 #[must_use]
189 pub fn get_decline_staking_reward(&self) -> Option<bool> {
190 self.data().decline_staking_reward
191 }
192
193 pub fn decline_staking_reward(&mut self, decline: bool) -> &mut Self {
195 self.data_mut().decline_staking_reward = Some(decline);
196 self
197 }
198}
199
200impl TransactionData for ContractUpdateTransactionData {}
201
202impl TransactionExecute for ContractUpdateTransactionData {
203 fn execute(
204 &self,
205 channel: Channel,
206 request: services::Transaction,
207 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
208 Box::pin(async { SmartContractServiceClient::new(channel).update_contract(request).await })
209 }
210}
211
212impl ValidateChecksums for ContractUpdateTransactionData {
213 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
214 self.contract_id.validate_checksums(ledger_id)?;
215 self.auto_renew_account_id.validate_checksums(ledger_id)?;
216 self.staked_id.validate_checksums(ledger_id)?;
217 self.proxy_account_id.validate_checksums(ledger_id)
218 }
219}
220
221impl ToTransactionDataProtobuf for ContractUpdateTransactionData {
222 fn to_transaction_data_protobuf(
223 &self,
224 chunk_info: &ChunkInfo,
225 ) -> services::transaction_body::Data {
226 let _ = chunk_info.assert_single_transaction();
227
228 services::transaction_body::Data::ContractUpdateInstance(self.to_protobuf())
229 }
230}
231
232impl ToSchedulableTransactionDataProtobuf for ContractUpdateTransactionData {
233 fn to_schedulable_transaction_data_protobuf(
234 &self,
235 ) -> services::schedulable_transaction_body::Data {
236 services::schedulable_transaction_body::Data::ContractUpdateInstance(self.to_protobuf())
237 }
238}
239
240impl FromProtobuf<services::ContractUpdateTransactionBody> for ContractUpdateTransactionData {
241 #[allow(deprecated)]
242 fn from_protobuf(pb: services::ContractUpdateTransactionBody) -> crate::Result<Self> {
243 use services::contract_update_transaction_body::MemoField;
244
245 Ok(Self {
246 contract_id: Option::from_protobuf(pb.contract_id)?,
247 expiration_time: pb.expiration_time.map(Into::into),
248 admin_key: Option::from_protobuf(pb.admin_key)?,
249 auto_renew_period: pb.auto_renew_period.map(Into::into),
250 contract_memo: pb.memo_field.map(|it| match it {
251 MemoField::Memo(it) | MemoField::MemoWrapper(it) => it,
252 }),
253 max_automatic_token_associations: pb.max_automatic_token_associations,
254 auto_renew_account_id: Option::from_protobuf(pb.auto_renew_account_id)?,
255 proxy_account_id: Option::from_protobuf(pb.proxy_account_id)?,
256 staked_id: Option::from_protobuf(pb.staked_id)?,
257 decline_staking_reward: pb.decline_reward,
258 })
259 }
260}
261
262impl ToProtobuf for ContractUpdateTransactionData {
263 type Protobuf = services::ContractUpdateTransactionBody;
264
265 fn to_protobuf(&self) -> Self::Protobuf {
266 let contract_id = self.contract_id.to_protobuf();
267 let expiration_time = self.expiration_time.map(Into::into);
268 let admin_key = self.admin_key.to_protobuf();
269 let auto_renew_period = self.auto_renew_period.map(Into::into);
270 let auto_renew_account_id = self.auto_renew_account_id.to_protobuf();
271
272 let staked_id = self.staked_id.map(|id| match id {
273 StakedId::NodeId(id) => {
274 services::contract_update_transaction_body::StakedId::StakedNodeId(id as i64)
275 }
276
277 StakedId::AccountId(id) => {
278 services::contract_update_transaction_body::StakedId::StakedAccountId(
279 id.to_protobuf(),
280 )
281 }
282 });
283
284 let memo_field = self
285 .contract_memo
286 .clone()
287 .map(services::contract_update_transaction_body::MemoField::MemoWrapper);
288
289 #[allow(deprecated)]
290 services::ContractUpdateTransactionBody {
291 contract_id,
292 expiration_time,
293 admin_key,
294 proxy_account_id: self.proxy_account_id.to_protobuf(),
295 auto_renew_period,
296 max_automatic_token_associations: self
297 .max_automatic_token_associations
298 .map(|max| max as i32),
299 auto_renew_account_id,
300 decline_reward: self.decline_staking_reward,
301 staked_id,
302 file_id: None,
303 memo_field,
304 }
305 }
306}
307
308impl From<ContractUpdateTransactionData> for AnyTransactionData {
309 fn from(transaction: ContractUpdateTransactionData) -> Self {
310 Self::ContractUpdate(transaction)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316
317 use expect_test::expect;
318 use hedera_proto::services;
319 use time::{
320 Duration,
321 OffsetDateTime,
322 };
323
324 use crate::contract::ContractUpdateTransactionData;
325 use crate::protobuf::{
326 FromProtobuf,
327 ToProtobuf,
328 };
329 use crate::transaction::test_helpers::{
330 check_body,
331 transaction_body,
332 unused_private_key,
333 };
334 use crate::{
335 AccountId,
336 AnyTransaction,
337 ContractId,
338 ContractUpdateTransaction,
339 PublicKey,
340 };
341
342 fn admin_key() -> PublicKey {
343 unused_private_key().public_key()
344 }
345
346 const CONTRACT_ID: ContractId = ContractId::new(0, 0, 5007);
347
348 const MAX_AUTOMATIC_TOKEN_ASSOCIATIONS: i32 = 101;
349 const AUTO_RENEW_PERIOD: Duration = Duration::days(1);
350 const CONTRACT_MEMO: &str = "3";
351 const EXPIRATION_TIME: OffsetDateTime =
352 match OffsetDateTime::from_unix_timestamp_nanos(4_000_000) {
353 Ok(it) => it,
354 Err(_) => panic!("Panic in `const` unwrap"),
355 };
356 const PROXY_ACCOUNT_ID: AccountId = AccountId::new(0, 0, 4);
357 const AUTO_RENEW_ACCOUNT_ID: AccountId = AccountId::new(0, 0, 30);
358 const STAKED_ACCOUNT_ID: AccountId = AccountId::new(0, 0, 3);
359 const STAKED_NODE_ID: u64 = 4;
360
361 fn make_transaction() -> ContractUpdateTransaction {
362 let mut tx = ContractUpdateTransaction::new_for_tests();
363
364 tx.contract_id(CONTRACT_ID)
365 .admin_key(admin_key())
366 .max_automatic_token_associations(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS)
367 .auto_renew_period(AUTO_RENEW_PERIOD)
368 .contract_memo(CONTRACT_MEMO)
369 .expiration_time(EXPIRATION_TIME)
370 .proxy_account_id(PROXY_ACCOUNT_ID)
371 .auto_renew_account_id(AUTO_RENEW_ACCOUNT_ID)
372 .staked_account_id(STAKED_ACCOUNT_ID)
373 .freeze()
374 .unwrap();
375
376 tx
377 }
378
379 fn make_transaction2() -> ContractUpdateTransaction {
380 let mut tx = ContractUpdateTransaction::new_for_tests();
381
382 tx.contract_id(CONTRACT_ID)
383 .admin_key(admin_key())
384 .max_automatic_token_associations(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS)
385 .auto_renew_period(AUTO_RENEW_PERIOD)
386 .contract_memo(CONTRACT_MEMO)
387 .expiration_time(EXPIRATION_TIME)
388 .proxy_account_id(PROXY_ACCOUNT_ID)
389 .auto_renew_account_id(AUTO_RENEW_ACCOUNT_ID)
390 .staked_node_id(STAKED_NODE_ID)
391 .freeze()
392 .unwrap();
393
394 tx
395 }
396
397 #[test]
398 fn serialize() {
399 let tx = make_transaction();
400
401 let tx = transaction_body(tx);
402
403 let tx = check_body(tx);
404
405 expect![[r#"
406 ContractUpdateInstance(
407 ContractUpdateTransactionBody {
408 contract_id: Some(
409 ContractId {
410 shard_num: 0,
411 realm_num: 0,
412 contract: Some(
413 ContractNum(
414 5007,
415 ),
416 ),
417 },
418 ),
419 expiration_time: Some(
420 Timestamp {
421 seconds: 0,
422 nanos: 4000000,
423 },
424 ),
425 admin_key: Some(
426 Key {
427 key: Some(
428 Ed25519(
429 [
430 224,
431 200,
432 236,
433 39,
434 88,
435 165,
436 135,
437 159,
438 250,
439 194,
440 38,
441 161,
442 60,
443 12,
444 81,
445 107,
446 121,
447 158,
448 114,
449 227,
450 81,
451 65,
452 160,
453 221,
454 130,
455 143,
456 148,
457 211,
458 121,
459 136,
460 164,
461 183,
462 ],
463 ),
464 ),
465 },
466 ),
467 proxy_account_id: Some(
468 AccountId {
469 shard_num: 0,
470 realm_num: 0,
471 account: Some(
472 AccountNum(
473 4,
474 ),
475 ),
476 },
477 ),
478 auto_renew_period: Some(
479 Duration {
480 seconds: 86400,
481 },
482 ),
483 file_id: None,
484 max_automatic_token_associations: Some(
485 101,
486 ),
487 auto_renew_account_id: Some(
488 AccountId {
489 shard_num: 0,
490 realm_num: 0,
491 account: Some(
492 AccountNum(
493 30,
494 ),
495 ),
496 },
497 ),
498 decline_reward: None,
499 memo_field: Some(
500 MemoWrapper(
501 "3",
502 ),
503 ),
504 staked_id: Some(
505 StakedAccountId(
506 AccountId {
507 shard_num: 0,
508 realm_num: 0,
509 account: Some(
510 AccountNum(
511 3,
512 ),
513 ),
514 },
515 ),
516 ),
517 },
518 )
519 "#]]
520 .assert_debug_eq(&tx)
521 }
522
523 #[test]
524 fn to_from_bytes() {
525 let tx = make_transaction();
526
527 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
528
529 let tx = transaction_body(tx);
530
531 let tx2 = transaction_body(tx2);
532
533 assert_eq!(tx, tx2);
534 }
535
536 #[test]
537 fn serialize2() {
538 let tx = make_transaction2();
539
540 let tx = transaction_body(tx);
541
542 let tx = check_body(tx);
543
544 expect![[r#"
545 ContractUpdateInstance(
546 ContractUpdateTransactionBody {
547 contract_id: Some(
548 ContractId {
549 shard_num: 0,
550 realm_num: 0,
551 contract: Some(
552 ContractNum(
553 5007,
554 ),
555 ),
556 },
557 ),
558 expiration_time: Some(
559 Timestamp {
560 seconds: 0,
561 nanos: 4000000,
562 },
563 ),
564 admin_key: Some(
565 Key {
566 key: Some(
567 Ed25519(
568 [
569 224,
570 200,
571 236,
572 39,
573 88,
574 165,
575 135,
576 159,
577 250,
578 194,
579 38,
580 161,
581 60,
582 12,
583 81,
584 107,
585 121,
586 158,
587 114,
588 227,
589 81,
590 65,
591 160,
592 221,
593 130,
594 143,
595 148,
596 211,
597 121,
598 136,
599 164,
600 183,
601 ],
602 ),
603 ),
604 },
605 ),
606 proxy_account_id: Some(
607 AccountId {
608 shard_num: 0,
609 realm_num: 0,
610 account: Some(
611 AccountNum(
612 4,
613 ),
614 ),
615 },
616 ),
617 auto_renew_period: Some(
618 Duration {
619 seconds: 86400,
620 },
621 ),
622 file_id: None,
623 max_automatic_token_associations: Some(
624 101,
625 ),
626 auto_renew_account_id: Some(
627 AccountId {
628 shard_num: 0,
629 realm_num: 0,
630 account: Some(
631 AccountNum(
632 30,
633 ),
634 ),
635 },
636 ),
637 decline_reward: None,
638 memo_field: Some(
639 MemoWrapper(
640 "3",
641 ),
642 ),
643 staked_id: Some(
644 StakedNodeId(
645 4,
646 ),
647 ),
648 },
649 )
650 "#]]
651 .assert_debug_eq(&tx)
652 }
653
654 #[test]
655 fn to_from_bytes2() {
656 let tx = make_transaction2();
657
658 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
659
660 let tx = transaction_body(tx);
661
662 let tx2 = transaction_body(tx2);
663
664 assert_eq!(tx, tx2);
665 }
666
667 #[test]
668 fn from_proto_body() {
669 #[allow(deprecated)]
670 let tx = services::ContractUpdateTransactionBody {
671 contract_id: Some(CONTRACT_ID.to_protobuf()),
672 expiration_time: Some(EXPIRATION_TIME.to_protobuf()),
673 admin_key: Some(admin_key().to_protobuf()),
674 proxy_account_id: Some(PROXY_ACCOUNT_ID.to_protobuf()),
675 auto_renew_period: Some(AUTO_RENEW_PERIOD.to_protobuf()),
676 max_automatic_token_associations: Some(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS),
677 auto_renew_account_id: Some(AUTO_RENEW_ACCOUNT_ID.to_protobuf()),
678 decline_reward: None,
679 memo_field: Some(services::contract_update_transaction_body::MemoField::MemoWrapper(
680 CONTRACT_MEMO.to_owned(),
681 )),
682 staked_id: Some(services::contract_update_transaction_body::StakedId::StakedAccountId(
683 STAKED_ACCOUNT_ID.to_protobuf(),
684 )),
685 file_id: None,
686 };
687
688 let tx = ContractUpdateTransactionData::from_protobuf(tx).unwrap();
689
690 assert_eq!(tx.contract_id, Some(CONTRACT_ID));
691 assert_eq!(tx.admin_key, Some(admin_key().into()));
692 assert_eq!(tx.max_automatic_token_associations, Some(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS));
693 assert_eq!(tx.auto_renew_period, Some(AUTO_RENEW_PERIOD));
694 assert_eq!(tx.contract_memo, Some(CONTRACT_MEMO.to_owned()));
695 assert_eq!(tx.expiration_time, Some(EXPIRATION_TIME));
696 assert_eq!(tx.proxy_account_id, Some(PROXY_ACCOUNT_ID));
697 assert_eq!(tx.auto_renew_account_id, Some(AUTO_RENEW_ACCOUNT_ID));
698 assert_eq!(tx.staked_id, Some(crate::staked_id::StakedId::AccountId(STAKED_ACCOUNT_ID)));
699 }
700
701 mod get_set {
702 use super::*;
703
704 #[test]
705 fn contract_id() {
706 let mut tx = ContractUpdateTransaction::new();
707 tx.contract_id(CONTRACT_ID);
708
709 assert_eq!(tx.get_contract_id(), Some(CONTRACT_ID));
710 }
711
712 #[test]
713 #[should_panic]
714 fn contract_id_frozen_panics() {
715 make_transaction().contract_id(CONTRACT_ID);
716 }
717
718 #[test]
719 fn admin_key() {
720 let mut tx = ContractUpdateTransaction::new();
721 tx.admin_key(super::admin_key());
722
723 assert_eq!(tx.get_admin_key(), Some(&super::admin_key().into()));
724 }
725
726 #[test]
727 #[should_panic]
728 fn admin_key_frozen_panics() {
729 make_transaction().admin_key(super::admin_key());
730 }
731
732 #[test]
733 fn max_automatic_token_associations() {
734 let mut tx = ContractUpdateTransaction::new();
735 tx.max_automatic_token_associations(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS);
736
737 assert_eq!(
738 tx.get_max_automatic_token_associations(),
739 Some(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS)
740 );
741 }
742
743 #[test]
744 #[should_panic]
745 fn max_automatic_token_associations_frozen_panics() {
746 make_transaction().max_automatic_token_associations(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS);
747 }
748
749 #[test]
750 fn auto_renew_period() {
751 let mut tx = ContractUpdateTransaction::new();
752 tx.auto_renew_period(AUTO_RENEW_PERIOD);
753
754 assert_eq!(tx.get_auto_renew_period(), Some(AUTO_RENEW_PERIOD));
755 }
756
757 #[test]
758 #[should_panic]
759 fn auto_renew_period_frozen_panics() {
760 make_transaction().auto_renew_period(AUTO_RENEW_PERIOD);
761 }
762
763 #[test]
764 fn contract_memo() {
765 let mut tx = ContractUpdateTransaction::new();
766 tx.contract_memo(CONTRACT_MEMO);
767
768 assert_eq!(tx.get_contract_memo(), Some(CONTRACT_MEMO));
769 }
770
771 #[test]
772 #[should_panic]
773 fn contract_memo_frozen_panics() {
774 make_transaction().contract_memo(CONTRACT_MEMO);
775 }
776
777 #[test]
778 fn expiration_time() {
779 let mut tx = ContractUpdateTransaction::new();
780 tx.expiration_time(EXPIRATION_TIME);
781
782 assert_eq!(tx.get_expiration_time(), Some(EXPIRATION_TIME));
783 }
784
785 #[test]
786 #[should_panic]
787 fn expiration_time_frozen_panics() {
788 make_transaction().expiration_time(EXPIRATION_TIME);
789 }
790
791 #[test]
792 fn proxy_account_id() {
793 let mut tx = ContractUpdateTransaction::new();
794 tx.proxy_account_id(PROXY_ACCOUNT_ID);
795
796 assert_eq!(tx.get_proxy_account_id(), Some(PROXY_ACCOUNT_ID));
797 }
798
799 #[test]
800 #[should_panic]
801 fn proxy_account_id_frozen_panics() {
802 make_transaction().proxy_account_id(PROXY_ACCOUNT_ID);
803 }
804
805 #[test]
806 fn auto_renew_account_id() {
807 let mut tx = ContractUpdateTransaction::new();
808 tx.auto_renew_account_id(AUTO_RENEW_ACCOUNT_ID);
809
810 assert_eq!(tx.get_auto_renew_account_id(), Some(AUTO_RENEW_ACCOUNT_ID));
811 }
812
813 #[test]
814 #[should_panic]
815 fn auto_renew_account_id_frozen_panics() {
816 make_transaction().auto_renew_account_id(AUTO_RENEW_ACCOUNT_ID);
817 }
818
819 #[test]
820 fn staked_account_id() {
821 let mut tx = ContractUpdateTransaction::new();
822 tx.staked_account_id(STAKED_ACCOUNT_ID);
823
824 assert_eq!(tx.get_staked_account_id(), Some(STAKED_ACCOUNT_ID));
825 }
826
827 #[test]
828 #[should_panic]
829 fn staked_account_id_frozen_panics() {
830 make_transaction().staked_account_id(STAKED_ACCOUNT_ID);
831 }
832
833 #[test]
834 fn staked_node_id() {
835 let mut tx = ContractUpdateTransaction::new();
836 tx.staked_node_id(STAKED_NODE_ID);
837
838 assert_eq!(tx.get_staked_node_id(), Some(STAKED_NODE_ID));
839 }
840
841 #[test]
842 #[should_panic]
843 fn staked_node_id_frozen_panics() {
844 make_transaction().staked_node_id(STAKED_NODE_ID);
845 }
846 }
847}