Skip to main content

hiero_sdk/system/
system_delete_transaction.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::file_service_client::FileServiceClient;
5use hiero_sdk_proto::services::smart_contract_service_client::SmartContractServiceClient;
6use time::OffsetDateTime;
7use tonic::transport::Channel;
8
9use crate::protobuf::{
10    FromProtobuf,
11    ToProtobuf,
12};
13use crate::transaction::{
14    AnyTransactionData,
15    ChunkInfo,
16    ToSchedulableTransactionDataProtobuf,
17    ToTransactionDataProtobuf,
18    TransactionData,
19    TransactionExecute,
20};
21use crate::{
22    BoxGrpcFuture,
23    ContractId,
24    Error,
25    FileId,
26    Transaction,
27    ValidateChecksums,
28};
29
30/// Delete a file or smart contract - can only be done by a Hiero admin.
31pub type SystemDeleteTransaction = Transaction<SystemDeleteTransactionData>;
32
33/// Delete a file or smart contract - can only be done by a Hiero admin.
34///
35/// When it is deleted, it immediately disappears from the system as seen by the user,
36/// but is still stored internally until the expiration time, at which time it
37/// is truly and permanently deleted.
38///
39/// Until that time, it can be undeleted by the Hiero admin.
40/// When a smart contract is deleted, the cryptocurrency account within it continues
41/// to exist, and is not affected by the expiration time here.
42///
43
44#[derive(Debug, Clone, Default)]
45pub struct SystemDeleteTransactionData {
46    expiration_time: Option<OffsetDateTime>,
47    file_id: Option<FileId>,
48    contract_id: Option<ContractId>,
49}
50
51impl SystemDeleteTransaction {
52    /// Returns the contract ID which should be deleted.
53    #[must_use]
54    pub fn get_contract_id(&self) -> Option<ContractId> {
55        self.data().contract_id
56    }
57
58    /// Sets the contract ID which should be deleted.
59    pub fn contract_id(&mut self, id: impl Into<ContractId>) -> &mut Self {
60        let data = self.data_mut();
61        data.file_id = None;
62        data.contract_id = Some(id.into());
63        self
64    }
65
66    /// Returns the file ID which should be deleted.
67    #[must_use]
68    pub fn get_file_id(&self) -> Option<FileId> {
69        self.data().file_id
70    }
71
72    /// Sets the file ID which should be deleted.
73    pub fn file_id(&mut self, id: impl Into<FileId>) -> &mut Self {
74        let data = self.data_mut();
75        data.contract_id = None;
76        data.file_id = Some(id.into());
77        self
78    }
79
80    /// Returns the timestamp at which the "deleted" entity should
81    /// truly be permanently deleted.
82    #[must_use]
83    pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
84        self.data().expiration_time
85    }
86
87    /// Sets the timestamp at which the "deleted" file should
88    /// truly be permanently deleted.
89    pub fn expiration_time(&mut self, expiration_time: OffsetDateTime) -> &mut Self {
90        self.data_mut().expiration_time = Some(expiration_time);
91        self
92    }
93}
94
95impl TransactionData for SystemDeleteTransactionData {}
96
97impl TransactionExecute for SystemDeleteTransactionData {
98    #[allow(deprecated)]
99    fn execute(
100        &self,
101        channel: Channel,
102        request: services::Transaction,
103    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
104        Box::pin(async move {
105            if self.file_id.is_some() {
106                FileServiceClient::new(channel).system_delete(request).await
107            } else {
108                SmartContractServiceClient::new(channel).system_delete(request).await
109            }
110        })
111    }
112}
113
114impl ValidateChecksums for SystemDeleteTransactionData {
115    fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
116        self.file_id.validate_checksums(ledger_id)?;
117        self.contract_id.validate_checksums(ledger_id)
118    }
119}
120
121impl ToTransactionDataProtobuf for SystemDeleteTransactionData {
122    fn to_transaction_data_protobuf(
123        &self,
124        chunk_info: &ChunkInfo,
125    ) -> services::transaction_body::Data {
126        let _ = chunk_info.assert_single_transaction();
127
128        services::transaction_body::Data::SystemDelete(self.to_protobuf())
129    }
130}
131
132impl ToSchedulableTransactionDataProtobuf for SystemDeleteTransactionData {
133    fn to_schedulable_transaction_data_protobuf(
134        &self,
135    ) -> services::schedulable_transaction_body::Data {
136        services::schedulable_transaction_body::Data::SystemDelete(self.to_protobuf())
137    }
138}
139
140impl From<SystemDeleteTransactionData> for AnyTransactionData {
141    fn from(transaction: SystemDeleteTransactionData) -> Self {
142        Self::SystemDelete(transaction)
143    }
144}
145
146impl FromProtobuf<services::SystemDeleteTransactionBody> for SystemDeleteTransactionData {
147    fn from_protobuf(pb: services::SystemDeleteTransactionBody) -> crate::Result<Self> {
148        use services::system_delete_transaction_body::Id;
149        let (file_id, contract_id) = match pb.id {
150            Some(Id::FileId(it)) => (Some(FileId::from_protobuf(it)?), None),
151            Some(Id::ContractId(it)) => (None, Some(ContractId::from_protobuf(it)?)),
152            None => (None, None),
153        };
154
155        Ok(Self { file_id, contract_id, expiration_time: pb.expiration_time.map(Into::into) })
156    }
157}
158
159impl ToProtobuf for SystemDeleteTransactionData {
160    type Protobuf = services::SystemDeleteTransactionBody;
161
162    fn to_protobuf(&self) -> Self::Protobuf {
163        let expiration_time = self.expiration_time.map(Into::into);
164        let contract_id = self.contract_id.to_protobuf();
165        let file_id = self.file_id.to_protobuf();
166
167        let id = match (contract_id, file_id) {
168            (Some(contract_id), _) => {
169                Some(services::system_delete_transaction_body::Id::ContractId(contract_id))
170            }
171
172            (_, Some(file_id)) => {
173                Some(services::system_delete_transaction_body::Id::FileId(file_id))
174            }
175
176            _ => None,
177        };
178
179        services::SystemDeleteTransactionBody { expiration_time, id }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use expect_test::expect;
186    use hiero_sdk_proto::services;
187
188    use crate::protobuf::{
189        FromProtobuf,
190        ToProtobuf,
191    };
192    use crate::system::SystemDeleteTransactionData;
193    use crate::transaction::test_helpers::{
194        check_body,
195        transaction_body,
196        VALID_START,
197    };
198    use crate::{
199        AnyTransaction,
200        ContractId,
201        FileId,
202        SystemDeleteTransaction,
203    };
204
205    const FILE_ID: FileId = FileId::new(0, 0, 444);
206    const CONTRACT_ID: ContractId = ContractId::new(0, 0, 444);
207
208    fn make_transaction_file() -> SystemDeleteTransaction {
209        let mut tx = SystemDeleteTransaction::new_for_tests();
210
211        tx.file_id(FILE_ID).expiration_time(VALID_START).freeze().unwrap();
212        tx
213    }
214
215    fn make_transaction_contract() -> SystemDeleteTransaction {
216        let mut tx = SystemDeleteTransaction::new_for_tests();
217
218        tx.contract_id(CONTRACT_ID).expiration_time(VALID_START).freeze().unwrap();
219        tx
220    }
221
222    #[test]
223    fn serialize_file() {
224        let tx = make_transaction_file();
225
226        let tx = transaction_body(tx);
227
228        let tx = check_body(tx);
229
230        expect![[r#"
231            SystemDelete(
232                SystemDeleteTransactionBody {
233                    expiration_time: Some(
234                        TimestampSeconds {
235                            seconds: 1554158542,
236                        },
237                    ),
238                    id: Some(
239                        FileId(
240                            FileId {
241                                shard_num: 0,
242                                realm_num: 0,
243                                file_num: 444,
244                            },
245                        ),
246                    ),
247                },
248            )
249        "#]]
250        .assert_debug_eq(&tx)
251    }
252
253    #[test]
254    fn serialize_contract() {
255        let tx = make_transaction_contract();
256
257        let tx = transaction_body(tx);
258
259        let tx = check_body(tx);
260
261        expect![[r#"
262            SystemDelete(
263                SystemDeleteTransactionBody {
264                    expiration_time: Some(
265                        TimestampSeconds {
266                            seconds: 1554158542,
267                        },
268                    ),
269                    id: Some(
270                        ContractId(
271                            ContractId {
272                                shard_num: 0,
273                                realm_num: 0,
274                                contract: Some(
275                                    ContractNum(
276                                        444,
277                                    ),
278                                ),
279                            },
280                        ),
281                    ),
282                },
283            )
284        "#]]
285        .assert_debug_eq(&tx)
286    }
287
288    #[test]
289    fn to_from_bytes_file() {
290        let tx = make_transaction_file();
291
292        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
293
294        let tx = transaction_body(tx);
295
296        let tx2 = transaction_body(tx2);
297
298        assert_eq!(tx, tx2);
299    }
300
301    #[test]
302    fn to_from_bytes_contract() {
303        let tx = make_transaction_contract();
304
305        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
306
307        let tx = transaction_body(tx);
308
309        let tx2 = transaction_body(tx2);
310
311        assert_eq!(tx, tx2);
312    }
313
314    #[test]
315    fn from_proto_body() {
316        let tx = services::SystemDeleteTransactionBody {
317            expiration_time: Some(services::TimestampSeconds {
318                seconds: VALID_START.unix_timestamp(),
319            }),
320            id: Some(services::system_delete_transaction_body::Id::FileId(FILE_ID.to_protobuf())),
321        };
322
323        let tx = SystemDeleteTransactionData::from_protobuf(tx).unwrap();
324
325        assert_eq!(tx.file_id, Some(FILE_ID));
326        assert_eq!(tx.contract_id, None);
327        assert_eq!(tx.expiration_time, Some(VALID_START));
328    }
329
330    #[test]
331    fn get_set_file_id() {
332        let mut tx = SystemDeleteTransaction::new();
333        tx.file_id(FILE_ID);
334
335        assert_eq!(tx.get_file_id(), Some(FILE_ID));
336    }
337
338    #[test]
339    #[should_panic]
340    fn get_set_file_id_frozen_panics() {
341        make_transaction_file().file_id(FILE_ID);
342    }
343
344    #[test]
345    fn get_set_contract_id() {
346        let mut tx = SystemDeleteTransaction::new();
347        tx.contract_id(CONTRACT_ID);
348
349        assert_eq!(tx.get_contract_id(), Some(CONTRACT_ID));
350    }
351
352    #[test]
353    #[should_panic]
354    fn get_set_contract_id_frozen_panics() {
355        make_transaction_file().contract_id(CONTRACT_ID);
356    }
357
358    #[test]
359    fn get_set_expiration_time() {
360        let mut tx = SystemDeleteTransaction::new();
361        tx.expiration_time(VALID_START);
362
363        assert_eq!(tx.get_expiration_time(), Some(VALID_START));
364    }
365
366    #[test]
367    #[should_panic]
368    fn get_set_expiration_time_frozen_panics() {
369        make_transaction_file().expiration_time(VALID_START);
370    }
371}