use crate::types::{MoveModuleId, TypeTag};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionPayload {
Script(Script),
#[doc(hidden)]
ModuleBundle(DeprecatedModuleBundle),
EntryFunction(EntryFunction),
Multisig(Multisig),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeprecatedModuleBundle {
#[doc(hidden)]
_private: (),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Multisig {
pub multisig_address: crate::types::AccountAddress,
pub transaction_payload: Option<MultisigTransactionPayload>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MultisigTransactionPayload {
EntryFunction(EntryFunction),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Script {
#[serde(with = "serde_bytes")]
pub code: Vec<u8>,
pub type_args: Vec<TypeTag>,
pub args: Vec<ScriptArgument>,
}
impl Script {
pub fn new(code: Vec<u8>, type_args: Vec<TypeTag>, args: Vec<ScriptArgument>) -> Self {
Self {
code,
type_args,
args,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScriptArgument {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
U128(u128),
U256([u8; 32]),
Address(crate::types::AccountAddress),
U8Vector(#[serde(with = "serde_bytes")] Vec<u8>),
Bool(bool),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EntryFunction {
pub module: MoveModuleId,
pub function: String,
pub type_args: Vec<TypeTag>,
pub args: Vec<Vec<u8>>,
}
impl EntryFunction {
pub fn new(
module: MoveModuleId,
function: impl Into<String>,
type_args: Vec<TypeTag>,
args: Vec<Vec<u8>>,
) -> Self {
Self {
module,
function: function.into(),
type_args,
args,
}
}
pub fn from_function_id(
function_id: &str,
type_args: Vec<TypeTag>,
args: Vec<Vec<u8>>,
) -> crate::error::AptosResult<Self> {
let func_id = crate::types::EntryFunctionId::from_str_strict(function_id)?;
Ok(Self {
module: func_id.module,
function: func_id.name.as_str().to_string(),
type_args,
args,
})
}
pub fn apt_transfer(
recipient: crate::types::AccountAddress,
amount: u64,
) -> crate::error::AptosResult<Self> {
let module = MoveModuleId::from_str_strict("0x1::aptos_account")?;
Ok(Self {
module,
function: "transfer".to_string(),
type_args: vec![],
args: vec![
aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
],
})
}
pub fn coin_transfer(
coin_type: TypeTag,
recipient: crate::types::AccountAddress,
amount: u64,
) -> crate::error::AptosResult<Self> {
let module = MoveModuleId::from_str_strict("0x1::coin")?;
Ok(Self {
module,
function: "transfer".to_string(),
type_args: vec![coin_type],
args: vec![
aptos_bcs::to_bytes(&recipient).map_err(crate::error::AptosError::bcs)?,
aptos_bcs::to_bytes(&amount).map_err(crate::error::AptosError::bcs)?,
],
})
}
}
impl From<EntryFunction> for TransactionPayload {
fn from(entry_function: EntryFunction) -> Self {
TransactionPayload::EntryFunction(entry_function)
}
}
impl From<Script> for TransactionPayload {
fn from(script: Script) -> Self {
TransactionPayload::Script(script)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::AccountAddress;
#[test]
fn test_apt_transfer() {
let recipient = AccountAddress::from_hex("0x123").unwrap();
let entry_fn = EntryFunction::apt_transfer(recipient, 1000).unwrap();
assert_eq!(entry_fn.function, "transfer");
assert!(entry_fn.type_args.is_empty());
assert_eq!(entry_fn.args.len(), 2);
}
#[test]
fn test_from_function_id() {
let entry_fn = EntryFunction::from_function_id(
"0x1::coin::transfer",
vec![TypeTag::aptos_coin()],
vec![],
)
.unwrap();
assert_eq!(entry_fn.module.address, AccountAddress::ONE);
assert_eq!(entry_fn.module.name.as_str(), "coin");
assert_eq!(entry_fn.function, "transfer");
}
#[test]
fn test_entry_function_new() {
let module = MoveModuleId::from_str_strict("0x1::test_module").unwrap();
let entry_fn = EntryFunction::new(
module.clone(),
"test_function",
vec![TypeTag::U64],
vec![vec![1, 2, 3]],
);
assert_eq!(entry_fn.module, module);
assert_eq!(entry_fn.function, "test_function");
assert_eq!(entry_fn.type_args.len(), 1);
assert_eq!(entry_fn.args.len(), 1);
}
#[test]
fn test_coin_transfer() {
let recipient = AccountAddress::from_hex("0x456").unwrap();
let coin_type = TypeTag::aptos_coin();
let entry_fn = EntryFunction::coin_transfer(coin_type, recipient, 5000).unwrap();
assert_eq!(entry_fn.module.address, AccountAddress::ONE);
assert_eq!(entry_fn.module.name.as_str(), "coin");
assert_eq!(entry_fn.function, "transfer");
assert_eq!(entry_fn.type_args.len(), 1);
assert_eq!(entry_fn.args.len(), 2);
}
#[test]
fn test_script_new() {
let code = vec![0x01, 0x02, 0x03];
let type_args = vec![TypeTag::U64];
let args = vec![ScriptArgument::U64(100)];
let script = Script::new(code.clone(), type_args.clone(), args);
assert_eq!(script.code, code);
assert_eq!(script.type_args.len(), 1);
assert_eq!(script.args.len(), 1);
}
#[test]
fn test_script_argument_variants() {
let u8_arg = ScriptArgument::U8(255);
let u16_arg = ScriptArgument::U16(65535);
let u32_arg = ScriptArgument::U32(4_294_967_295);
let u64_arg = ScriptArgument::U64(18_446_744_073_709_551_615);
let u128_arg = ScriptArgument::U128(340_282_366_920_938_463_463_374_607_431_768_211_455);
let bool_arg = ScriptArgument::Bool(true);
let addr_arg = ScriptArgument::Address(AccountAddress::ONE);
let bytes_arg = ScriptArgument::U8Vector(vec![1, 2, 3]);
let u256_arg = ScriptArgument::U256([0xff; 32]);
let args = vec![
u8_arg.clone(),
u16_arg.clone(),
u32_arg.clone(),
u64_arg.clone(),
u128_arg.clone(),
bool_arg.clone(),
addr_arg.clone(),
bytes_arg.clone(),
u256_arg.clone(),
];
for arg in args {
let serialized = aptos_bcs::to_bytes(&arg).unwrap();
let deserialized: ScriptArgument = aptos_bcs::from_bytes(&serialized).unwrap();
assert_eq!(deserialized, arg);
}
}
#[test]
fn test_transaction_payload_from_entry_function() {
let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
let payload: TransactionPayload = entry_fn.into();
match payload {
TransactionPayload::EntryFunction(ef) => {
assert_eq!(ef.function, "transfer");
}
_ => panic!("Expected EntryFunction variant"),
}
}
#[test]
fn test_transaction_payload_from_script() {
let script = Script::new(vec![0x01], vec![], vec![]);
let payload: TransactionPayload = script.into();
match payload {
TransactionPayload::Script(s) => {
assert_eq!(s.code, vec![0x01]);
}
_ => panic!("Expected Script variant"),
}
}
#[test]
fn test_multisig_payload() {
let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 100).unwrap();
let multisig = Multisig {
multisig_address: AccountAddress::from_hex("0x999").unwrap(),
transaction_payload: Some(MultisigTransactionPayload::EntryFunction(entry_fn)),
};
let payload = TransactionPayload::Multisig(multisig.clone());
match payload {
TransactionPayload::Multisig(m) => {
assert_eq!(m.multisig_address, multisig.multisig_address);
assert!(m.transaction_payload.is_some());
}
_ => panic!("Expected Multisig variant"),
}
}
#[test]
fn test_entry_function_bcs_serialization() {
let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
let serialized = aptos_bcs::to_bytes(&entry_fn).unwrap();
let deserialized: EntryFunction = aptos_bcs::from_bytes(&serialized).unwrap();
assert_eq!(entry_fn.module, deserialized.module);
assert_eq!(entry_fn.function, deserialized.function);
assert_eq!(entry_fn.type_args, deserialized.type_args);
assert_eq!(entry_fn.args, deserialized.args);
}
#[test]
fn test_transaction_payload_bcs_serialization() {
let entry_fn = EntryFunction::apt_transfer(AccountAddress::ONE, 1000).unwrap();
let payload = TransactionPayload::EntryFunction(entry_fn);
let serialized = aptos_bcs::to_bytes(&payload).unwrap();
let deserialized: TransactionPayload = aptos_bcs::from_bytes(&serialized).unwrap();
assert_eq!(payload, deserialized);
}
#[test]
fn test_from_function_id_invalid() {
let result = EntryFunction::from_function_id("0x1coin::transfer", vec![], vec![]);
assert!(result.is_err());
let result = EntryFunction::from_function_id("invalid::module::function", vec![], vec![]);
assert!(result.is_err());
}
}