1use crate::types::{MoveModuleId, TypeTag};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TransactionPayload {
15 Script(Script),
17 #[doc(hidden)]
20 ModuleBundle(DeprecatedModuleBundle),
21 EntryFunction(EntryFunction),
23 Multisig(Multisig),
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub struct DeprecatedModuleBundle {
31 #[doc(hidden)]
32 _private: (),
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
37pub struct Multisig {
38 pub multisig_address: crate::types::AccountAddress,
40 pub transaction_payload: Option<MultisigTransactionPayload>,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub enum MultisigTransactionPayload {
47 EntryFunction(EntryFunction),
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
53pub struct Script {
54 #[serde(with = "serde_bytes")]
56 pub code: Vec<u8>,
57 pub type_args: Vec<TypeTag>,
59 pub args: Vec<ScriptArgument>,
61}
62
63impl Script {
64 pub fn new(code: Vec<u8>, type_args: Vec<TypeTag>, args: Vec<ScriptArgument>) -> Self {
66 Self {
67 code,
68 type_args,
69 args,
70 }
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76pub enum ScriptArgument {
77 U8(u8),
79 U16(u16),
81 U32(u32),
83 U64(u64),
85 U128(u128),
87 U256([u8; 32]),
89 Address(crate::types::AccountAddress),
91 U8Vector(#[serde(with = "serde_bytes")] Vec<u8>),
93 Bool(bool),
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
122pub struct EntryFunction {
123 pub module: MoveModuleId,
125 pub function: String,
127 pub type_args: Vec<TypeTag>,
129 pub args: Vec<Vec<u8>>,
131}
132
133impl EntryFunction {
134 pub fn new(
136 module: MoveModuleId,
137 function: impl Into<String>,
138 type_args: Vec<TypeTag>,
139 args: Vec<Vec<u8>>,
140 ) -> Self {
141 Self {
142 module,
143 function: function.into(),
144 type_args,
145 args,
146 }
147 }
148
149 pub fn from_function_id(
161 function_id: &str,
162 type_args: Vec<TypeTag>,
163 args: Vec<Vec<u8>>,
164 ) -> crate::error::AptosResult<Self> {
165 let func_id = crate::types::EntryFunctionId::from_str_strict(function_id)?;
166 Ok(Self {
167 module: func_id.module,
168 function: func_id.name.as_str().to_string(),
169 type_args,
170 args,
171 })
172 }
173
174 pub fn apt_transfer(
185 recipient: crate::types::AccountAddress,
186 amount: u64,
187 ) -> crate::error::AptosResult<Self> {
188 let module = MoveModuleId::from_str_strict("0x1::aptos_account")?;
189 Ok(Self {
190 module,
191 function: "transfer".to_string(),
192 type_args: vec![],
193 args: vec![
194 aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
195 aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
196 ],
197 })
198 }
199
200 pub fn coin_transfer(
212 coin_type: TypeTag,
213 recipient: crate::types::AccountAddress,
214 amount: u64,
215 ) -> crate::error::AptosResult<Self> {
216 let module = MoveModuleId::from_str_strict("0x1::coin")?;
217 Ok(Self {
218 module,
219 function: "transfer".to_string(),
220 type_args: vec![coin_type],
221 args: vec![
222 aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
223 aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
224 ],
225 })
226 }
227}
228
229impl From<EntryFunction> for TransactionPayload {
230 fn from(entry_function: EntryFunction) -> Self {
231 TransactionPayload::EntryFunction(entry_function)
232 }
233}
234
235impl From<Script> for TransactionPayload {
236 fn from(script: Script) -> Self {
237 TransactionPayload::Script(script)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::types::AccountAddress;
245
246 #[test]
247 fn test_apt_transfer() {
248 let recipient = AccountAddress::from_hex("0x123").unwrap();
249 let entry_fn = EntryFunction::apt_transfer(recipient, 1000).unwrap();
250
251 assert_eq!(entry_fn.function, "transfer");
252 assert!(entry_fn.type_args.is_empty());
253 assert_eq!(entry_fn.args.len(), 2);
254 }
255
256 #[test]
257 fn test_from_function_id() {
258 let entry_fn = EntryFunction::from_function_id(
259 "0x1::coin::transfer",
260 vec![TypeTag::aptos_coin()],
261 vec![],
262 )
263 .unwrap();
264
265 assert_eq!(entry_fn.module.address, AccountAddress::ONE);
266 assert_eq!(entry_fn.module.name.as_str(), "coin");
267 assert_eq!(entry_fn.function, "transfer");
268 }
269
270 #[test]
271 fn test_entry_function_new() {
272 let module = MoveModuleId::from_str_strict("0x1::test_module").unwrap();
273 let entry_fn = EntryFunction::new(
274 module.clone(),
275 "test_function",
276 vec![TypeTag::U64],
277 vec![vec![1, 2, 3]],
278 );
279
280 assert_eq!(entry_fn.module, module);
281 assert_eq!(entry_fn.function, "test_function");
282 assert_eq!(entry_fn.type_args.len(), 1);
283 assert_eq!(entry_fn.args.len(), 1);
284 }
285
286 #[test]
287 fn test_coin_transfer() {
288 let recipient = AccountAddress::from_hex("0x456").unwrap();
289 let coin_type = TypeTag::aptos_coin();
290 let entry_fn = EntryFunction::coin_transfer(coin_type, recipient, 5000).unwrap();
291
292 assert_eq!(entry_fn.module.address, AccountAddress::ONE);
293 assert_eq!(entry_fn.module.name.as_str(), "coin");
294 assert_eq!(entry_fn.function, "transfer");
295 assert_eq!(entry_fn.type_args.len(), 1);
296 assert_eq!(entry_fn.args.len(), 2);
297 }
298
299 #[test]
300 fn test_script_new() {
301 let code = vec![0x01, 0x02, 0x03];
302 let type_args = vec![TypeTag::U64];
303 let args = vec![ScriptArgument::U64(100)];
304 let script = Script::new(code.clone(), type_args.clone(), args);
305
306 assert_eq!(script.code, code);
307 assert_eq!(script.type_args.len(), 1);
308 assert_eq!(script.args.len(), 1);
309 }
310
311 #[test]
312 fn test_script_argument_variants() {
313 let u8_arg = ScriptArgument::U8(255);
314 let u16_arg = ScriptArgument::U16(65535);
315 let u32_arg = ScriptArgument::U32(4_294_967_295);
316 let u64_arg = ScriptArgument::U64(18_446_744_073_709_551_615);
317 let u128_arg = ScriptArgument::U128(340_282_366_920_938_463_463_374_607_431_768_211_455);
318 let bool_arg = ScriptArgument::Bool(true);
319 let addr_arg = ScriptArgument::Address(AccountAddress::ONE);
320 let bytes_arg = ScriptArgument::U8Vector(vec![1, 2, 3]);
321 let u256_arg = ScriptArgument::U256([0xff; 32]);
322
323 let args = vec![
325 u8_arg.clone(),
326 u16_arg.clone(),
327 u32_arg.clone(),
328 u64_arg.clone(),
329 u128_arg.clone(),
330 bool_arg.clone(),
331 addr_arg.clone(),
332 bytes_arg.clone(),
333 u256_arg.clone(),
334 ];
335
336 for arg in args {
337 let serialized = aptos_bcs::to_bytes(&arg).unwrap();
338 let deserialized: ScriptArgument = aptos_bcs::from_bytes(&serialized).unwrap();
339 assert_eq!(deserialized, arg);
340 }
341 }
342
343 #[test]
344 fn test_transaction_payload_from_entry_function() {
345 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
346 let payload: TransactionPayload = entry_fn.into();
347
348 match payload {
349 TransactionPayload::EntryFunction(ef) => {
350 assert_eq!(ef.function, "transfer");
351 }
352 _ => panic!("Expected EntryFunction variant"),
353 }
354 }
355
356 #[test]
357 fn test_transaction_payload_from_script() {
358 let script = Script::new(vec![0x01], vec![], vec![]);
359 let payload: TransactionPayload = script.into();
360
361 match payload {
362 TransactionPayload::Script(s) => {
363 assert_eq!(s.code, vec![0x01]);
364 }
365 _ => panic!("Expected Script variant"),
366 }
367 }
368
369 #[test]
370 fn test_multisig_payload() {
371 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
372 let multisig = Multisig {
373 multisig_address: AccountAddress::from_hex("0x999").unwrap(),
374 transaction_payload: Some(MultisigTransactionPayload::EntryFunction(entry_fn)),
375 };
376
377 let payload = TransactionPayload::Multisig(multisig.clone());
378 match payload {
379 TransactionPayload::Multisig(m) => {
380 assert_eq!(m.multisig_address, multisig.multisig_address);
381 assert!(m.transaction_payload.is_some());
382 }
383 _ => panic!("Expected Multisig variant"),
384 }
385 }
386
387 #[test]
388 fn test_entry_function_bcs_serialization() {
389 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
390 let serialized = aptos_bcs::to_bytes(&entry_fn).unwrap();
391 let deserialized: EntryFunction = aptos_bcs::from_bytes(&serialized).unwrap();
392
393 assert_eq!(entry_fn.module, deserialized.module);
394 assert_eq!(entry_fn.function, deserialized.function);
395 assert_eq!(entry_fn.type_args, deserialized.type_args);
396 assert_eq!(entry_fn.args, deserialized.args);
397 }
398
399 #[test]
400 fn test_transaction_payload_bcs_serialization() {
401 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
402 let payload = TransactionPayload::EntryFunction(entry_fn);
403
404 let serialized = aptos_bcs::to_bytes(&payload).unwrap();
405 let deserialized: TransactionPayload = aptos_bcs::from_bytes(&serialized).unwrap();
406
407 assert_eq!(payload, deserialized);
408 }
409
410 #[test]
411 fn test_from_function_id_invalid() {
412 let result = EntryFunction::from_function_id("0x1coin::transfer", vec![], vec![]);
414 assert!(result.is_err());
415
416 let result = EntryFunction::from_function_id("invalid::module::function", vec![], vec![]);
418 assert!(result.is_err());
419 }
420}