1use crate::types::{MoveModuleId, TypeTag};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum TransactionPayload {
17 Script(Script),
19 #[doc(hidden)]
22 ModuleBundle(DeprecatedModuleBundle),
23 EntryFunction(EntryFunction),
25 Multisig(Multisig),
27 Payload(TransactionPayloadInner),
34}
35
36impl TransactionPayload {
37 pub fn into_orderless(self, nonce: u64) -> crate::error::AptosResult<Self> {
55 let executable = match self {
56 TransactionPayload::EntryFunction(ef) => TransactionExecutable::EntryFunction(ef),
57 TransactionPayload::Script(s) => TransactionExecutable::Script(s),
58 other => {
59 return Err(crate::error::AptosError::Transaction(format!(
60 "orderless transactions support only entry-function or script payloads, got {}",
61 other.variant_name()
62 )));
63 }
64 };
65 Ok(TransactionPayload::Payload(TransactionPayloadInner::V1 {
66 executable,
67 extra_config: TransactionExtraConfig::V1 {
68 multisig_address: None,
69 replay_protection_nonce: Some(nonce),
70 },
71 }))
72 }
73
74 fn variant_name(&self) -> &'static str {
76 match self {
77 TransactionPayload::Script(_) => "Script",
78 TransactionPayload::ModuleBundle(_) => "ModuleBundle",
79 TransactionPayload::EntryFunction(_) => "EntryFunction",
80 TransactionPayload::Multisig(_) => "Multisig",
81 TransactionPayload::Payload(_) => "Payload",
82 }
83 }
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub enum TransactionPayloadInner {
91 V1 {
93 executable: TransactionExecutable,
95 extra_config: TransactionExtraConfig,
97 },
98}
99
100#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
107pub enum TransactionExecutable {
108 Script(Script),
110 EntryFunction(EntryFunction),
112 Empty,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
121pub enum TransactionExtraConfig {
122 V1 {
124 multisig_address: Option<crate::types::AccountAddress>,
126 replay_protection_nonce: Option<u64>,
128 },
129}
130
131#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
134pub struct DeprecatedModuleBundle {
135 #[doc(hidden)]
136 _private: (),
137}
138
139#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
141pub struct Multisig {
142 pub multisig_address: crate::types::AccountAddress,
144 pub transaction_payload: Option<MultisigTransactionPayload>,
146}
147
148#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
150pub enum MultisigTransactionPayload {
151 EntryFunction(EntryFunction),
153}
154
155#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
157pub struct Script {
158 #[serde(with = "serde_bytes")]
160 pub code: Vec<u8>,
161 pub type_args: Vec<TypeTag>,
163 pub args: Vec<ScriptArgument>,
165}
166
167impl Script {
168 pub fn new(code: Vec<u8>, type_args: Vec<TypeTag>, args: Vec<ScriptArgument>) -> Self {
170 Self {
171 code,
172 type_args,
173 args,
174 }
175 }
176}
177
178#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
183pub enum ScriptArgument {
184 U8(u8),
186 U64(u64),
188 U128(u128),
190 Address(crate::types::AccountAddress),
192 U8Vector(#[serde(with = "serde_bytes")] Vec<u8>),
194 Bool(bool),
196 U16(u16),
198 U32(u32),
200 U256([u8; 32]),
202 Serialized(#[serde(with = "serde_bytes")] Vec<u8>),
204 I8(i8),
206 I16(i16),
208 I32(i32),
210 I64(i64),
212 I128(i128),
214 I256([u8; 32]),
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
243pub struct EntryFunction {
244 pub module: MoveModuleId,
246 pub function: String,
248 pub type_args: Vec<TypeTag>,
250 pub args: Vec<Vec<u8>>,
252}
253
254impl EntryFunction {
255 pub fn new(
257 module: MoveModuleId,
258 function: impl Into<String>,
259 type_args: Vec<TypeTag>,
260 args: Vec<Vec<u8>>,
261 ) -> Self {
262 Self {
263 module,
264 function: function.into(),
265 type_args,
266 args,
267 }
268 }
269
270 pub fn from_function_id(
282 function_id: &str,
283 type_args: Vec<TypeTag>,
284 args: Vec<Vec<u8>>,
285 ) -> crate::error::AptosResult<Self> {
286 let func_id = crate::types::EntryFunctionId::from_str_strict(function_id)?;
287 Ok(Self {
288 module: func_id.module,
289 function: func_id.name.as_str().to_string(),
290 type_args,
291 args,
292 })
293 }
294
295 pub fn apt_transfer(
306 recipient: crate::types::AccountAddress,
307 amount: u64,
308 ) -> crate::error::AptosResult<Self> {
309 let module = MoveModuleId::from_str_strict("0x1::aptos_account")?;
310 Ok(Self {
311 module,
312 function: "transfer".to_string(),
313 type_args: vec![],
314 args: vec![
315 aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
316 aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
317 ],
318 })
319 }
320
321 pub fn coin_transfer(
333 coin_type: TypeTag,
334 recipient: crate::types::AccountAddress,
335 amount: u64,
336 ) -> crate::error::AptosResult<Self> {
337 let module = MoveModuleId::from_str_strict("0x1::coin")?;
338 Ok(Self {
339 module,
340 function: "transfer".to_string(),
341 type_args: vec![coin_type],
342 args: vec![
343 aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
344 aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
345 ],
346 })
347 }
348}
349
350impl From<EntryFunction> for TransactionPayload {
351 fn from(entry_function: EntryFunction) -> Self {
352 TransactionPayload::EntryFunction(entry_function)
353 }
354}
355
356impl From<Script> for TransactionPayload {
357 fn from(script: Script) -> Self {
358 TransactionPayload::Script(script)
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::types::AccountAddress;
366
367 #[test]
368 fn test_apt_transfer() {
369 let recipient = AccountAddress::from_hex("0x123").unwrap();
370 let entry_fn = EntryFunction::apt_transfer(recipient, 1000).unwrap();
371
372 assert_eq!(entry_fn.function, "transfer");
373 assert!(entry_fn.type_args.is_empty());
374 assert_eq!(entry_fn.args.len(), 2);
375 }
376
377 #[test]
378 fn test_from_function_id() {
379 let entry_fn = EntryFunction::from_function_id(
380 "0x1::coin::transfer",
381 vec![TypeTag::aptos_coin()],
382 vec![],
383 )
384 .unwrap();
385
386 assert_eq!(entry_fn.module.address, AccountAddress::ONE);
387 assert_eq!(entry_fn.module.name.as_str(), "coin");
388 assert_eq!(entry_fn.function, "transfer");
389 }
390
391 #[test]
392 fn test_entry_function_new() {
393 let module = MoveModuleId::from_str_strict("0x1::test_module").unwrap();
394 let entry_fn = EntryFunction::new(
395 module.clone(),
396 "test_function",
397 vec![TypeTag::U64],
398 vec![vec![1, 2, 3]],
399 );
400
401 assert_eq!(entry_fn.module, module);
402 assert_eq!(entry_fn.function, "test_function");
403 assert_eq!(entry_fn.type_args.len(), 1);
404 assert_eq!(entry_fn.args.len(), 1);
405 }
406
407 #[test]
408 fn test_coin_transfer() {
409 let recipient = AccountAddress::from_hex("0x456").unwrap();
410 let coin_type = TypeTag::aptos_coin();
411 let entry_fn = EntryFunction::coin_transfer(coin_type, recipient, 5000).unwrap();
412
413 assert_eq!(entry_fn.module.address, AccountAddress::ONE);
414 assert_eq!(entry_fn.module.name.as_str(), "coin");
415 assert_eq!(entry_fn.function, "transfer");
416 assert_eq!(entry_fn.type_args.len(), 1);
417 assert_eq!(entry_fn.args.len(), 2);
418 }
419
420 #[test]
421 fn test_into_orderless_wraps_entry_function() {
422 let ef = EntryFunction::apt_transfer(AccountAddress::ONE, 1).unwrap();
423 let payload = TransactionPayload::EntryFunction(ef.clone())
424 .into_orderless(0xdead_beef)
425 .unwrap();
426
427 match payload {
428 TransactionPayload::Payload(TransactionPayloadInner::V1 {
429 executable: TransactionExecutable::EntryFunction(inner),
430 extra_config:
431 TransactionExtraConfig::V1 {
432 multisig_address,
433 replay_protection_nonce,
434 },
435 }) => {
436 assert_eq!(inner, ef);
437 assert_eq!(multisig_address, None);
438 assert_eq!(replay_protection_nonce, Some(0xdead_beef));
439 }
440 other => panic!("expected orderless Payload, got {other:?}"),
441 }
442 }
443
444 #[test]
445 fn test_into_orderless_wraps_script() {
446 let script = Script::new(vec![0x00], vec![], vec![]);
447 let payload = TransactionPayload::Script(script.clone())
448 .into_orderless(7)
449 .unwrap();
450
451 assert!(matches!(
452 payload,
453 TransactionPayload::Payload(TransactionPayloadInner::V1 {
454 executable: TransactionExecutable::Script(_),
455 ..
456 })
457 ));
458 }
459
460 #[test]
461 fn test_into_orderless_rejects_multisig() {
462 let multisig = Multisig {
463 multisig_address: AccountAddress::ONE,
464 transaction_payload: None,
465 };
466 let err = TransactionPayload::Multisig(multisig)
467 .into_orderless(1)
468 .unwrap_err();
469 assert!(matches!(err, crate::error::AptosError::Transaction(_)));
470 }
471
472 #[test]
473 fn test_script_new() {
474 let code = vec![0x01, 0x02, 0x03];
475 let type_args = vec![TypeTag::U64];
476 let args = vec![ScriptArgument::U64(100)];
477 let script = Script::new(code.clone(), type_args.clone(), args);
478
479 assert_eq!(script.code, code);
480 assert_eq!(script.type_args.len(), 1);
481 assert_eq!(script.args.len(), 1);
482 }
483
484 #[test]
485 fn test_script_argument_variants() {
486 let u8_arg = ScriptArgument::U8(255);
487 let u64_arg = ScriptArgument::U64(18_446_744_073_709_551_615);
488 let u128_arg = ScriptArgument::U128(340_282_366_920_938_463_463_374_607_431_768_211_455);
489 let addr_arg = ScriptArgument::Address(AccountAddress::ONE);
490 let bytes_arg = ScriptArgument::U8Vector(vec![1, 2, 3]);
491 let bool_arg = ScriptArgument::Bool(true);
492 let u16_arg = ScriptArgument::U16(65535);
493 let u32_arg = ScriptArgument::U32(4_294_967_295);
494 let u256_arg = ScriptArgument::U256([0xff; 32]);
495 let serialized_arg = ScriptArgument::Serialized(vec![1, 2, 3]);
496 let i64_arg = ScriptArgument::I64(-1i64);
497
498 let args = vec![
500 u8_arg.clone(),
501 u64_arg.clone(),
502 u128_arg.clone(),
503 addr_arg.clone(),
504 bytes_arg.clone(),
505 bool_arg.clone(),
506 u16_arg.clone(),
507 u32_arg.clone(),
508 u256_arg.clone(),
509 serialized_arg.clone(),
510 i64_arg.clone(),
511 ];
512
513 for arg in args {
514 let serialized = aptos_bcs::to_bytes(&arg).unwrap();
515 let deserialized: ScriptArgument = aptos_bcs::from_bytes(&serialized).unwrap();
516 assert_eq!(deserialized, arg);
517 }
518 }
519
520 #[test]
521 fn test_transaction_payload_from_entry_function() {
522 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
523 let payload: TransactionPayload = entry_fn.into();
524
525 match payload {
526 TransactionPayload::EntryFunction(ef) => {
527 assert_eq!(ef.function, "transfer");
528 }
529 _ => panic!("Expected EntryFunction variant"),
530 }
531 }
532
533 #[test]
534 fn test_transaction_payload_from_script() {
535 let script = Script::new(vec![0x01], vec![], vec![]);
536 let payload: TransactionPayload = script.into();
537
538 match payload {
539 TransactionPayload::Script(s) => {
540 assert_eq!(s.code, vec![0x01]);
541 }
542 _ => panic!("Expected Script variant"),
543 }
544 }
545
546 #[test]
547 fn test_multisig_payload() {
548 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
549 let multisig = Multisig {
550 multisig_address: AccountAddress::from_hex("0x999").unwrap(),
551 transaction_payload: Some(MultisigTransactionPayload::EntryFunction(entry_fn)),
552 };
553
554 let payload = TransactionPayload::Multisig(multisig.clone());
555 match payload {
556 TransactionPayload::Multisig(m) => {
557 assert_eq!(m.multisig_address, multisig.multisig_address);
558 assert!(m.transaction_payload.is_some());
559 }
560 _ => panic!("Expected Multisig variant"),
561 }
562 }
563
564 #[test]
565 fn test_entry_function_bcs_serialization() {
566 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
567 let serialized = aptos_bcs::to_bytes(&entry_fn).unwrap();
568 let deserialized: EntryFunction = aptos_bcs::from_bytes(&serialized).unwrap();
569
570 assert_eq!(entry_fn.module, deserialized.module);
571 assert_eq!(entry_fn.function, deserialized.function);
572 assert_eq!(entry_fn.type_args, deserialized.type_args);
573 assert_eq!(entry_fn.args, deserialized.args);
574 }
575
576 #[test]
577 fn test_transaction_payload_bcs_serialization() {
578 let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
579 let payload = TransactionPayload::EntryFunction(entry_fn);
580
581 let serialized = aptos_bcs::to_bytes(&payload).unwrap();
582 let deserialized: TransactionPayload = aptos_bcs::from_bytes(&serialized).unwrap();
583
584 assert_eq!(payload, deserialized);
585 }
586
587 #[test]
588 fn test_from_function_id_invalid() {
589 let result = EntryFunction::from_function_id("0x1coin::transfer", vec![], vec![]);
591 assert!(result.is_err());
592
593 let result = EntryFunction::from_function_id("invalid::module::function", vec![], vec![]);
595 assert!(result.is_err());
596 }
597}