1use 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
28pub type ContractExecuteTransaction = Transaction<ContractExecuteTransactionData>;
39
40#[derive(Default, Debug, Clone)]
41pub struct ContractExecuteTransactionData {
42 contract_id: Option<ContractId>,
44
45 gas: u64,
47
48 payable_amount: Hbar,
50
51 function_parameters: Vec<u8>,
53}
54
55impl ContractExecuteTransaction {
56 #[must_use]
58 pub fn get_contract_id(&self) -> Option<ContractId> {
59 self.data().contract_id
60 }
61
62 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 #[must_use]
70 pub fn get_gas(&self) -> u64 {
71 self.data().gas
72 }
73
74 pub fn gas(&mut self, gas: u64) -> &mut Self {
76 self.data_mut().gas = gas;
77 self
78 }
79
80 #[must_use]
82 pub fn get_payable_amount(&self) -> Hbar {
83 self.data().payable_amount
84 }
85
86 pub fn payable_amount(&mut self, amount: Hbar) -> &mut Self {
88 self.data_mut().payable_amount = amount;
89 self
90 }
91
92 #[must_use]
94 pub fn get_function_parameters(&self) -> &[u8] {
95 &self.data().function_parameters
96 }
97
98 pub fn function_parameters(&mut self, data: Vec<u8>) -> &mut Self {
100 self.data_mut().function_parameters = data;
101 self
102 }
103
104 pub fn function(&mut self, name: &str) -> &mut Self {
106 self.function_with_parameters(name, &ContractFunctionParameters::new())
107 }
108
109 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}