Skip to main content

hiero_sdk/contract/
contract_delete_transaction.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::protobuf::{
9    FromProtobuf,
10    ToProtobuf,
11};
12use crate::transaction::{
13    AnyTransactionData,
14    ChunkInfo,
15    ToSchedulableTransactionDataProtobuf,
16    ToTransactionDataProtobuf,
17    TransactionData,
18    TransactionExecute,
19};
20use crate::{
21    AccountId,
22    BoxGrpcFuture,
23    ContractId,
24    Error,
25    Transaction,
26    ValidateChecksums,
27};
28
29/// Marks a contract as deleted and transfers its remaining hBars, if any, to
30/// a designated receiver.
31///
32pub type ContractDeleteTransaction = Transaction<ContractDeleteTransactionData>;
33
34#[derive(Debug, Clone, Default)]
35pub struct ContractDeleteTransactionData {
36    contract_id: Option<ContractId>,
37
38    transfer_account_id: Option<AccountId>,
39
40    transfer_contract_id: Option<ContractId>,
41
42    permanent_removal: bool,
43}
44
45impl ContractDeleteTransaction {
46    /// Returns the ID of the contract that should be deleted.
47    #[must_use]
48    pub fn get_contract_id(&self) -> Option<ContractId> {
49        self.data().contract_id
50    }
51
52    /// Sets ID of the contract which should be deleted.
53    pub fn contract_id(&mut self, id: ContractId) -> &mut Self {
54        self.data_mut().contract_id = Some(id);
55        self
56    }
57
58    /// Returns the ID of the account which will receive all remaining hbars.
59    #[must_use]
60    pub fn get_transfer_account_id(&self) -> Option<AccountId> {
61        self.data().transfer_account_id
62    }
63
64    /// Sets the ID of the account which will receive all remaining hbars.
65    ///
66    /// Note: Clears any previously set transfer_contract_id (oneOf field).
67    pub fn transfer_account_id(&mut self, id: AccountId) -> &mut Self {
68        let data = self.data_mut();
69        data.transfer_account_id = Some(id);
70        data.transfer_contract_id = None; // Clear the other (oneOf)
71        self
72    }
73
74    /// Returns ID of the contract which will receive all rmaining hbars.
75    #[must_use]
76    pub fn get_transfer_contract_id(&self) -> Option<ContractId> {
77        self.data().transfer_contract_id
78    }
79
80    /// Sets the the ID of the contract which will receive all remaining hbars.
81    ///
82    /// Note: Clears any previously set transfer_account_id (oneOf field).
83    pub fn transfer_contract_id(&mut self, id: ContractId) -> &mut Self {
84        let data = self.data_mut();
85        data.transfer_contract_id = Some(id);
86        data.transfer_account_id = None; // Clear the other (oneOf)
87        self
88    }
89
90    /// Returns whether this is a permanent removal transaction.
91    #[must_use]
92    pub fn get_permanent_removal(&self) -> bool {
93        self.data().permanent_removal
94    }
95
96    /// Sets whether this is a permanent removal transaction.
97    ///
98    /// When true, the contract is permanently removed and cannot be recovered.
99    pub fn permanent_removal(&mut self, permanent: bool) -> &mut Self {
100        self.data_mut().permanent_removal = permanent;
101        self
102    }
103}
104
105impl TransactionData for ContractDeleteTransactionData {}
106
107impl TransactionExecute for ContractDeleteTransactionData {
108    fn execute(
109        &self,
110        channel: Channel,
111        request: services::Transaction,
112    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
113        Box::pin(async { SmartContractServiceClient::new(channel).delete_contract(request).await })
114    }
115}
116
117impl ValidateChecksums for ContractDeleteTransactionData {
118    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
119        self.contract_id.validate_checksums(ledger_id)?;
120        self.transfer_account_id.validate_checksums(ledger_id)?;
121        self.transfer_contract_id.validate_checksums(ledger_id)
122    }
123}
124
125impl ToTransactionDataProtobuf for ContractDeleteTransactionData {
126    fn to_transaction_data_protobuf(
127        &self,
128        chunk_info: &ChunkInfo,
129    ) -> services::transaction_body::Data {
130        let _ = chunk_info.assert_single_transaction();
131
132        services::transaction_body::Data::ContractDeleteInstance(self.to_protobuf())
133    }
134}
135
136impl ToSchedulableTransactionDataProtobuf for ContractDeleteTransactionData {
137    fn to_schedulable_transaction_data_protobuf(
138        &self,
139    ) -> services::schedulable_transaction_body::Data {
140        services::schedulable_transaction_body::Data::ContractDeleteInstance(self.to_protobuf())
141    }
142}
143
144impl FromProtobuf<services::ContractDeleteTransactionBody> for ContractDeleteTransactionData {
145    fn from_protobuf(pb: services::ContractDeleteTransactionBody) -> crate::Result<Self> {
146        use services::contract_delete_transaction_body::Obtainers;
147
148        let (transfer_account_id, transfer_contract_id) = match pb.obtainers {
149            Some(Obtainers::TransferAccountId(it)) => (Some(AccountId::from_protobuf(it)?), None),
150            Some(Obtainers::TransferContractId(it)) => (None, Some(ContractId::from_protobuf(it)?)),
151            None => (None, None),
152        };
153
154        Ok(Self {
155            contract_id: Option::from_protobuf(pb.contract_id)?,
156            transfer_account_id,
157            transfer_contract_id,
158            permanent_removal: pb.permanent_removal,
159        })
160    }
161}
162
163impl From<ContractDeleteTransactionData> for AnyTransactionData {
164    fn from(transaction: ContractDeleteTransactionData) -> Self {
165        Self::ContractDelete(transaction)
166    }
167}
168
169impl ToProtobuf for ContractDeleteTransactionData {
170    type Protobuf = services::ContractDeleteTransactionBody;
171
172    fn to_protobuf(&self) -> Self::Protobuf {
173        let delete_contract_id = self.contract_id.to_protobuf();
174
175        let obtainers = match (&self.transfer_account_id, &self.transfer_contract_id) {
176            (Some(account_id), None) => {
177                Some(services::contract_delete_transaction_body::Obtainers::TransferAccountId(
178                    account_id.to_protobuf(),
179                ))
180            }
181
182            (None, Some(contract_id)) => {
183                Some(services::contract_delete_transaction_body::Obtainers::TransferContractId(
184                    contract_id.to_protobuf(),
185                ))
186            }
187
188            (Some(account_id), Some(_)) => {
189                Some(services::contract_delete_transaction_body::Obtainers::TransferAccountId(
190                    account_id.to_protobuf(),
191                ))
192            }
193
194            _ => None,
195        };
196
197        services::ContractDeleteTransactionBody {
198            contract_id: delete_contract_id,
199            permanent_removal: self.permanent_removal,
200            obtainers,
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use expect_test::expect;
208    use hiero_sdk_proto::services;
209
210    use crate::contract::ContractDeleteTransactionData;
211    use crate::protobuf::{
212        FromProtobuf,
213        ToProtobuf,
214    };
215    use crate::transaction::test_helpers::{
216        check_body,
217        transaction_body,
218    };
219    use crate::{
220        AccountId,
221        AnyTransaction,
222        ContractDeleteTransaction,
223        ContractId,
224    };
225
226    const CONTRACT_ID: ContractId = ContractId::new(0, 0, 5007);
227    const TRANSFER_ACCOUNT_ID: AccountId = AccountId::new(0, 0, 9);
228    const TRANSFER_CONTRACT_ID: ContractId = ContractId::new(0, 0, 5008);
229
230    fn make_transaction() -> ContractDeleteTransaction {
231        let mut tx = ContractDeleteTransaction::new_for_tests();
232
233        tx.contract_id(CONTRACT_ID)
234            .transfer_account_id(TRANSFER_ACCOUNT_ID)
235            .transfer_contract_id(TRANSFER_CONTRACT_ID)
236            .freeze()
237            .unwrap();
238
239        tx
240    }
241
242    #[test]
243    fn serialize() {
244        let tx = make_transaction();
245
246        let tx = transaction_body(tx);
247
248        let tx = check_body(tx);
249
250        expect![[r#"
251            ContractDeleteInstance(
252                ContractDeleteTransactionBody {
253                    contract_id: Some(
254                        ContractId {
255                            shard_num: 0,
256                            realm_num: 0,
257                            contract: Some(
258                                ContractNum(
259                                    5007,
260                                ),
261                            ),
262                        },
263                    ),
264                    permanent_removal: false,
265                    obtainers: Some(
266                        TransferContractId(
267                            ContractId {
268                                shard_num: 0,
269                                realm_num: 0,
270                                contract: Some(
271                                    ContractNum(
272                                        5008,
273                                    ),
274                                ),
275                            },
276                        ),
277                    ),
278                },
279            )
280        "#]]
281        .assert_debug_eq(&tx)
282    }
283
284    #[test]
285    fn to_from_bytes() {
286        let tx = make_transaction();
287
288        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
289
290        let tx = transaction_body(tx);
291
292        let tx2 = transaction_body(tx2);
293
294        assert_eq!(tx, tx2);
295    }
296
297    #[test]
298    fn from_proto_body() {
299        let tx = services::ContractDeleteTransactionBody {
300            contract_id: Some(CONTRACT_ID.to_protobuf()),
301            obtainers: Some(
302                services::contract_delete_transaction_body::Obtainers::TransferAccountId(
303                    TRANSFER_ACCOUNT_ID.to_protobuf(),
304                ),
305            ),
306            permanent_removal: false,
307        };
308
309        let tx = ContractDeleteTransactionData::from_protobuf(tx).unwrap();
310
311        assert_eq!(tx.contract_id, Some(CONTRACT_ID));
312        assert_eq!(tx.transfer_account_id, Some(TRANSFER_ACCOUNT_ID));
313        assert_eq!(tx.transfer_contract_id, None);
314    }
315
316    #[test]
317    fn get_set_contract_id() {
318        let mut tx = ContractDeleteTransaction::new();
319        tx.contract_id(CONTRACT_ID);
320
321        assert_eq!(tx.get_contract_id(), Some(CONTRACT_ID));
322    }
323
324    #[test]
325    #[should_panic]
326    fn get_set_contract_id_frozen_panics() {
327        make_transaction().contract_id(CONTRACT_ID);
328    }
329
330    #[test]
331    fn get_set_transfer_account_id() {
332        let mut tx = ContractDeleteTransaction::new();
333        tx.transfer_account_id(TRANSFER_ACCOUNT_ID);
334
335        assert_eq!(tx.get_transfer_account_id(), Some(TRANSFER_ACCOUNT_ID));
336    }
337
338    #[test]
339    #[should_panic]
340    fn get_set_transfer_account_id_frozen_panics() {
341        make_transaction().transfer_account_id(TRANSFER_ACCOUNT_ID);
342    }
343
344    #[test]
345    fn get_set_transfer_contract_id() {
346        let mut tx = ContractDeleteTransaction::new();
347        tx.transfer_contract_id(TRANSFER_CONTRACT_ID);
348
349        assert_eq!(tx.get_transfer_contract_id(), Some(TRANSFER_CONTRACT_ID));
350    }
351
352    #[test]
353    #[should_panic]
354    fn get_set_transfer_contract_id_frozen_panics() {
355        make_transaction().transfer_contract_id(TRANSFER_CONTRACT_ID);
356    }
357}