1use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::file_service_client::FileServiceClient;
5use time::{
6 Duration,
7 OffsetDateTime,
8};
9use tonic::transport::Channel;
10
11use crate::ledger_id::RefLedgerId;
12use crate::protobuf::{
13 FromProtobuf,
14 ToProtobuf,
15};
16use crate::transaction::{
17 AnyTransactionData,
18 ChunkInfo,
19 ToSchedulableTransactionDataProtobuf,
20 ToTransactionDataProtobuf,
21 TransactionData,
22 TransactionExecute,
23};
24use crate::{
25 AccountId,
26 BoxGrpcFuture,
27 Error,
28 FileId,
29 Key,
30 KeyList,
31 Transaction,
32 ValidateChecksums,
33};
34
35pub type FileUpdateTransaction = Transaction<FileUpdateTransactionData>;
41
42#[derive(Debug, Clone, Default)]
43pub struct FileUpdateTransactionData {
44 file_id: Option<FileId>,
46
47 file_memo: Option<String>,
49
50 keys: Option<KeyList>,
54
55 contents: Option<Vec<u8>>,
57
58 expiration_time: Option<OffsetDateTime>,
60
61 auto_renew_account_id: Option<AccountId>,
62
63 auto_renew_period: Option<Duration>,
64}
65
66impl FileUpdateTransaction {
67 #[must_use]
69 pub fn get_file_id(&self) -> Option<FileId> {
70 self.data().file_id
71 }
72
73 pub fn file_id(&mut self, id: impl Into<FileId>) -> &mut Self {
75 self.data_mut().file_id = Some(id.into());
76 self
77 }
78
79 #[must_use]
81 pub fn get_file_memo(&self) -> Option<&str> {
82 self.data().file_memo.as_deref()
83 }
84
85 pub fn file_memo(&mut self, memo: impl Into<String>) -> &mut Self {
87 self.data_mut().file_memo = Some(memo.into());
88 self
89 }
90
91 #[must_use]
93 pub fn get_contents(&self) -> Option<&[u8]> {
94 self.data().contents.as_deref()
95 }
96
97 pub fn contents(&mut self, contents: Vec<u8>) -> &mut Self {
99 self.data_mut().contents = Some(contents);
100 self
101 }
102
103 #[must_use]
105 pub fn get_keys(&self) -> Option<&KeyList> {
106 self.data().keys.as_ref()
107 }
108
109 pub fn keys<K: Into<Key>>(&mut self, keys: impl IntoIterator<Item = K>) -> &mut Self {
115 self.data_mut().keys = Some(keys.into_iter().map(Into::into).collect());
116 self
117 }
118
119 #[must_use]
121 pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
122 self.data().expiration_time
123 }
124
125 pub fn expiration_time(&mut self, at: OffsetDateTime) -> &mut Self {
127 self.data_mut().expiration_time = Some(at);
128 self
129 }
130
131 #[must_use]
137 pub fn get_auto_renew_account_id(&self) -> Option<AccountId> {
138 self.data().auto_renew_account_id
139 }
140
141 pub fn auto_renew_account_id(&mut self, id: AccountId) -> &mut Self {
147 self.data_mut().auto_renew_account_id = Some(id);
148 self
149 }
150
151 #[must_use]
156 pub fn get_auto_renew_period(&self) -> Option<Duration> {
157 self.data().auto_renew_period
158 }
159
160 pub fn auto_renew_period(&mut self, duration: Duration) -> &mut Self {
165 self.data_mut().auto_renew_period = Some(duration);
166 self
167 }
168}
169
170impl TransactionData for FileUpdateTransactionData {}
171
172impl TransactionExecute for FileUpdateTransactionData {
173 fn execute(
174 &self,
175 channel: Channel,
176 request: services::Transaction,
177 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
178 Box::pin(async { FileServiceClient::new(channel).update_file(request).await })
179 }
180}
181
182impl ValidateChecksums for FileUpdateTransactionData {
183 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
184 self.file_id.validate_checksums(ledger_id)
185 }
186}
187
188impl ToTransactionDataProtobuf for FileUpdateTransactionData {
189 fn to_transaction_data_protobuf(
190 &self,
191 chunk_info: &ChunkInfo,
192 ) -> services::transaction_body::Data {
193 let _ = chunk_info.assert_single_transaction();
194
195 services::transaction_body::Data::FileUpdate(self.to_protobuf())
196 }
197}
198
199impl ToSchedulableTransactionDataProtobuf for FileUpdateTransactionData {
200 fn to_schedulable_transaction_data_protobuf(
201 &self,
202 ) -> services::schedulable_transaction_body::Data {
203 services::schedulable_transaction_body::Data::FileUpdate(self.to_protobuf())
204 }
205}
206
207impl From<FileUpdateTransactionData> for AnyTransactionData {
208 fn from(transaction: FileUpdateTransactionData) -> Self {
209 Self::FileUpdate(transaction)
210 }
211}
212
213impl FromProtobuf<services::FileUpdateTransactionBody> for FileUpdateTransactionData {
214 fn from_protobuf(pb: services::FileUpdateTransactionBody) -> crate::Result<Self> {
215 Ok(Self {
216 file_id: Option::from_protobuf(pb.file_id)?,
217 file_memo: pb.memo,
218 keys: Option::from_protobuf(pb.keys)?,
219 contents: Some(pb.contents),
220 expiration_time: pb.expiration_time.map(Into::into),
221 auto_renew_account_id: None,
222 auto_renew_period: None,
223 })
224 }
225}
226
227impl ToProtobuf for FileUpdateTransactionData {
228 type Protobuf = services::FileUpdateTransactionBody;
229
230 fn to_protobuf(&self) -> Self::Protobuf {
231 services::FileUpdateTransactionBody {
232 file_id: self.file_id.to_protobuf(),
233 expiration_time: self.expiration_time.to_protobuf(),
234 keys: self.keys.to_protobuf(),
235 contents: self.contents.clone().unwrap_or_default(),
236 memo: self.file_memo.clone(),
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use expect_test::expect;
244 use hiero_sdk_proto::services;
245 use time::OffsetDateTime;
246
247 use crate::file::FileUpdateTransactionData;
248 use crate::protobuf::{
249 FromProtobuf,
250 ToProtobuf,
251 };
252 use crate::transaction::test_helpers::{
253 check_body,
254 transaction_body,
255 unused_private_key,
256 };
257 use crate::{
258 AnyTransaction,
259 FileId,
260 FileUpdateTransaction,
261 Key,
262 KeyList,
263 };
264
265 const FILE_ID: FileId = FileId::new(0, 0, 6006);
266
267 const CONTENTS: [u8; 5] = [1, 2, 3, 4, 5];
268
269 const EXPIRATION_TIME: OffsetDateTime = match OffsetDateTime::from_unix_timestamp(1554158728) {
270 Ok(it) => it,
271 Err(_) => panic!("Panic in `const` unwrap"),
272 };
273
274 fn keys() -> impl IntoIterator<Item = Key> {
275 [unused_private_key().public_key().into()]
276 }
277
278 const FILE_MEMO: &str = "new memo";
279
280 fn make_transaction() -> FileUpdateTransaction {
281 let mut tx = FileUpdateTransaction::new_for_tests();
282
283 tx.file_id(FILE_ID)
284 .expiration_time(EXPIRATION_TIME)
285 .contents(CONTENTS.into())
286 .keys(keys())
287 .file_memo(FILE_MEMO)
288 .freeze()
289 .unwrap();
290
291 tx
292 }
293
294 #[test]
295 fn serialize() {
296 let tx = make_transaction();
297
298 let tx = transaction_body(tx);
299
300 let tx = check_body(tx);
301
302 expect![[r#"
303 FileUpdate(
304 FileUpdateTransactionBody {
305 file_id: Some(
306 FileId {
307 shard_num: 0,
308 realm_num: 0,
309 file_num: 6006,
310 },
311 ),
312 expiration_time: Some(
313 Timestamp {
314 seconds: 1554158728,
315 nanos: 0,
316 },
317 ),
318 keys: Some(
319 KeyList {
320 keys: [
321 Key {
322 key: Some(
323 Ed25519(
324 [
325 224,
326 200,
327 236,
328 39,
329 88,
330 165,
331 135,
332 159,
333 250,
334 194,
335 38,
336 161,
337 60,
338 12,
339 81,
340 107,
341 121,
342 158,
343 114,
344 227,
345 81,
346 65,
347 160,
348 221,
349 130,
350 143,
351 148,
352 211,
353 121,
354 136,
355 164,
356 183,
357 ],
358 ),
359 ),
360 },
361 ],
362 },
363 ),
364 contents: [
365 1,
366 2,
367 3,
368 4,
369 5,
370 ],
371 memo: Some(
372 "new memo",
373 ),
374 },
375 )
376 "#]]
377 .assert_debug_eq(&tx)
378 }
379
380 #[test]
381 fn to_from_bytes() {
382 let tx = make_transaction();
383
384 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
385
386 let tx = transaction_body(tx);
387
388 let tx2 = transaction_body(tx2);
389
390 assert_eq!(tx, tx2);
391 }
392
393 #[test]
394 fn from_proto_body() {
395 let tx = services::FileUpdateTransactionBody {
396 file_id: Some(FILE_ID.to_protobuf()),
397 expiration_time: Some(EXPIRATION_TIME.to_protobuf()),
398 keys: Some(KeyList::from_iter(keys()).to_protobuf()),
399 contents: CONTENTS.into(),
400 memo: Some(FILE_MEMO.to_owned()),
401 };
402
403 let tx = FileUpdateTransactionData::from_protobuf(tx).unwrap();
404
405 assert_eq!(tx.contents.as_deref(), Some(CONTENTS.as_slice()));
406 assert_eq!(tx.expiration_time, Some(EXPIRATION_TIME));
407 assert_eq!(tx.keys, Some(KeyList::from_iter(keys())));
408 assert_eq!(tx.file_memo.as_deref(), Some(FILE_MEMO));
409 }
410
411 mod get_set {
412 use super::*;
413
414 #[test]
415 fn contents() {
416 let mut tx = FileUpdateTransaction::new();
417 tx.contents(CONTENTS.into());
418
419 assert_eq!(tx.get_contents(), Some(CONTENTS.as_slice()));
420 }
421
422 #[test]
423 #[should_panic]
424 fn contents_frozen_panics() {
425 make_transaction().contents(CONTENTS.into());
426 }
427
428 #[test]
429 fn expiration_time() {
430 let mut tx = FileUpdateTransaction::new();
431 tx.expiration_time(EXPIRATION_TIME);
432
433 assert_eq!(tx.get_expiration_time(), Some(EXPIRATION_TIME));
434 }
435
436 #[test]
437 #[should_panic]
438 fn expiration_time_frozen_panics() {
439 make_transaction().expiration_time(EXPIRATION_TIME);
440 }
441
442 #[test]
443 fn keys() {
444 let mut tx = FileUpdateTransaction::new();
445 tx.keys(super::keys());
446
447 assert_eq!(tx.get_keys(), Some(&KeyList::from_iter(super::keys())));
448 }
449
450 #[test]
451 #[should_panic]
452 fn keys_frozen_panics() {
453 make_transaction().keys(super::keys());
454 }
455
456 #[test]
457 fn file_memo() {
458 let mut tx = FileUpdateTransaction::new();
459 tx.file_memo(FILE_MEMO);
460
461 assert_eq!(tx.get_file_memo(), Some(FILE_MEMO));
462 }
463
464 #[test]
465 #[should_panic]
466 fn file_memo_frozen_panics() {
467 make_transaction().file_memo(FILE_MEMO);
468 }
469 }
470}