hedera/contract/
contract_execute_transaction.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hedera_proto::services;
4use hedera_proto::services::smart_contract_service_client::SmartContractServiceClient;
5use tonic::transport::Channel;
6
7use crate::ledger_id::RefLedgerId;
8use crate::protobuf::FromProtobuf;
9use crate::transaction::{
10    AnyTransactionData,
11    ChunkInfo,
12    ToSchedulableTransactionDataProtobuf,
13    ToTransactionDataProtobuf,
14    TransactionData,
15    TransactionExecute,
16};
17use crate::{
18    BoxGrpcFuture,
19    ContractFunctionParameters,
20    ContractId,
21    Error,
22    Hbar,
23    ToProtobuf,
24    Transaction,
25    ValidateChecksums,
26};
27
28/// Call a function of the given smart contract instance, giving it
29/// parameters as its inputs.
30///
31/// It can use the given amount of gas, and any unspent gas will
32/// be refunded to the paying account.
33///
34/// If this function stores information, it is charged gas to store it.
35/// There is a fee in hbars to maintain that storage until the expiration time,
36/// and that fee is added as part of the transaction fee.
37///
38pub type ContractExecuteTransaction = Transaction<ContractExecuteTransactionData>;
39
40#[derive(Default, Debug, Clone)]
41pub struct ContractExecuteTransactionData {
42    /// The contract instance to call.
43    contract_id: Option<ContractId>,
44
45    /// The maximum amount of gas to use for the call.
46    gas: u64,
47
48    /// The number of hbars sent with this function call.
49    payable_amount: Hbar,
50
51    /// The function parameters as their raw bytes.
52    function_parameters: Vec<u8>,
53}
54
55impl ContractExecuteTransaction {
56    /// Returns the contract instance to call.
57    #[must_use]
58    pub fn get_contract_id(&self) -> Option<ContractId> {
59        self.data().contract_id
60    }
61
62    /// Sets the contract instance to call.
63    pub fn contract_id(&mut self, contract_id: ContractId) -> &mut Self {
64        self.data_mut().contract_id = Some(contract_id);
65        self
66    }
67
68    /// Returns the maximum amount of gas to use for the call.
69    #[must_use]
70    pub fn get_gas(&self) -> u64 {
71        self.data().gas
72    }
73
74    /// Sets the maximum amount of gas to use for the call.
75    pub fn gas(&mut self, gas: u64) -> &mut Self {
76        self.data_mut().gas = gas;
77        self
78    }
79
80    /// Returns the number of hbars to be sent with this function call.
81    #[must_use]
82    pub fn get_payable_amount(&self) -> Hbar {
83        self.data().payable_amount
84    }
85
86    /// Sets the number of hbars to be sent with this function call.
87    pub fn payable_amount(&mut self, amount: Hbar) -> &mut Self {
88        self.data_mut().payable_amount = amount;
89        self
90    }
91
92    /// Returns the function parameters as their raw bytes.
93    #[must_use]
94    pub fn get_function_parameters(&self) -> &[u8] {
95        &self.data().function_parameters
96    }
97
98    /// Sets the function parameters as their raw bytes.
99    pub fn function_parameters(&mut self, data: Vec<u8>) -> &mut Self {
100        self.data_mut().function_parameters = data;
101        self
102    }
103
104    /// Sets the function with no parameters.
105    pub fn function(&mut self, name: &str) -> &mut Self {
106        self.function_with_parameters(name, &ContractFunctionParameters::new())
107    }
108
109    /// Sets the function with parameters.
110    pub fn function_with_parameters(
111        &mut self,
112        name: &str,
113        parameters: &ContractFunctionParameters,
114    ) -> &mut Self {
115        self.function_parameters(parameters.to_bytes(Some(name)))
116    }
117}
118
119impl TransactionData for ContractExecuteTransactionData {}
120
121impl TransactionExecute for ContractExecuteTransactionData {
122    fn execute(
123        &self,
124        channel: Channel,
125        request: services::Transaction,
126    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
127        Box::pin(async {
128            SmartContractServiceClient::new(channel).contract_call_method(request).await
129        })
130    }
131}
132
133impl ValidateChecksums for ContractExecuteTransactionData {
134    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
135        self.contract_id.validate_checksums(ledger_id)?;
136        Ok(())
137    }
138}
139
140impl ToTransactionDataProtobuf for ContractExecuteTransactionData {
141    fn to_transaction_data_protobuf(
142        &self,
143        chunk_info: &ChunkInfo,
144    ) -> services::transaction_body::Data {
145        let _ = chunk_info.assert_single_transaction();
146
147        services::transaction_body::Data::ContractCall(self.to_protobuf())
148    }
149}
150
151impl ToSchedulableTransactionDataProtobuf for ContractExecuteTransactionData {
152    fn to_schedulable_transaction_data_protobuf(
153        &self,
154    ) -> services::schedulable_transaction_body::Data {
155        services::schedulable_transaction_body::Data::ContractCall(self.to_protobuf())
156    }
157}
158
159impl From<ContractExecuteTransactionData> for AnyTransactionData {
160    fn from(transaction: ContractExecuteTransactionData) -> Self {
161        Self::ContractExecute(transaction)
162    }
163}
164
165impl FromProtobuf<services::ContractCallTransactionBody> for ContractExecuteTransactionData {
166    fn from_protobuf(pb: services::ContractCallTransactionBody) -> crate::Result<Self>
167    where
168        Self: Sized,
169    {
170        Ok(Self {
171            contract_id: Option::from_protobuf(pb.contract_id)?,
172            gas: pb.gas as u64,
173            payable_amount: Hbar::from_tinybars(pb.amount),
174            function_parameters: pb.function_parameters,
175        })
176    }
177}
178
179impl ToProtobuf for ContractExecuteTransactionData {
180    type Protobuf = services::ContractCallTransactionBody;
181
182    fn to_protobuf(&self) -> Self::Protobuf {
183        #[allow(deprecated)]
184        services::ContractCallTransactionBody {
185            gas: self.gas as i64,
186            amount: self.payable_amount.to_tinybars(),
187            contract_id: self.contract_id.to_protobuf(),
188            function_parameters: self.function_parameters.clone(),
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use expect_test::expect;
196    use hedera_proto::services;
197
198    use crate::contract::ContractExecuteTransactionData;
199    use crate::protobuf::{
200        FromProtobuf,
201        ToProtobuf,
202    };
203    use crate::transaction::test_helpers::{
204        check_body,
205        transaction_body,
206    };
207    use crate::{
208        AnyTransaction,
209        ContractExecuteTransaction,
210        ContractId,
211        Hbar,
212    };
213
214    const CONTRACT_ID: ContractId = ContractId::new(0, 0, 5007);
215    const GAS: u64 = 10;
216    const PAYABLE_AMOUNT: Hbar = Hbar::from_tinybars(1000);
217
218    fn function_parameters() -> Vec<u8> {
219        Vec::from([24, 43, 11])
220    }
221
222    fn make_transaction() -> ContractExecuteTransaction {
223        let mut tx = ContractExecuteTransaction::new_for_tests();
224
225        tx.contract_id(CONTRACT_ID)
226            .gas(GAS)
227            .payable_amount(PAYABLE_AMOUNT)
228            .function_parameters(function_parameters())
229            .freeze()
230            .unwrap();
231
232        tx
233    }
234
235    #[test]
236    fn serialize() {
237        let tx = make_transaction();
238
239        let tx = transaction_body(tx);
240
241        let tx = check_body(tx);
242
243        expect![[r#"
244            ContractCall(
245                ContractCallTransactionBody {
246                    contract_id: Some(
247                        ContractId {
248                            shard_num: 0,
249                            realm_num: 0,
250                            contract: Some(
251                                ContractNum(
252                                    5007,
253                                ),
254                            ),
255                        },
256                    ),
257                    gas: 10,
258                    amount: 1000,
259                    function_parameters: [
260                        24,
261                        43,
262                        11,
263                    ],
264                },
265            )
266        "#]]
267        .assert_debug_eq(&tx)
268    }
269
270    #[test]
271    fn to_from_bytes() {
272        let tx = make_transaction();
273
274        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
275
276        let tx = transaction_body(tx);
277
278        let tx2 = transaction_body(tx2);
279
280        assert_eq!(tx, tx2);
281    }
282
283    #[test]
284    fn from_proto_body() {
285        let tx = services::ContractCallTransactionBody {
286            contract_id: Some(CONTRACT_ID.to_protobuf()),
287            gas: GAS as _,
288            amount: PAYABLE_AMOUNT.to_tinybars(),
289            function_parameters: function_parameters(),
290        };
291
292        let tx = ContractExecuteTransactionData::from_protobuf(tx).unwrap();
293
294        assert_eq!(tx.contract_id, Some(CONTRACT_ID));
295        assert_eq!(tx.gas, GAS);
296        assert_eq!(tx.payable_amount, PAYABLE_AMOUNT);
297        assert_eq!(tx.function_parameters, function_parameters());
298    }
299
300    #[test]
301    fn get_set_contract_id() {
302        let mut tx = ContractExecuteTransaction::new();
303        tx.contract_id(CONTRACT_ID);
304
305        assert_eq!(tx.get_contract_id(), Some(CONTRACT_ID));
306    }
307
308    #[test]
309    #[should_panic]
310    fn get_set_contract_id_frozen_panics() {
311        make_transaction().contract_id(CONTRACT_ID);
312    }
313
314    #[test]
315    fn get_set_gas() {
316        let mut tx = ContractExecuteTransaction::new();
317        tx.gas(GAS);
318
319        assert_eq!(tx.get_gas(), GAS);
320    }
321
322    #[test]
323    #[should_panic]
324    fn get_set_gas_frozen_panics() {
325        make_transaction().gas(GAS);
326    }
327
328    #[test]
329    fn get_set_payable_amount() {
330        let mut tx = ContractExecuteTransaction::new();
331        tx.payable_amount(PAYABLE_AMOUNT);
332
333        assert_eq!(tx.get_payable_amount(), PAYABLE_AMOUNT);
334    }
335
336    #[test]
337    #[should_panic]
338    fn get_set_payable_amount_frozen_panics() {
339        make_transaction().payable_amount(PAYABLE_AMOUNT);
340    }
341
342    #[test]
343    fn get_set_function_parameters() {
344        let mut tx = ContractExecuteTransaction::new();
345        tx.function_parameters(function_parameters());
346
347        assert_eq!(tx.get_function_parameters(), function_parameters());
348    }
349
350    #[test]
351    #[should_panic]
352    fn get_set_function_parameters_frozen_panics() {
353        make_transaction().function_parameters(function_parameters());
354    }
355}