diff --git a/crates/avalanche-types/src/avm/txs/export.rs b/crates/avalanche-types/src/avm/txs/export.rs index 12f553c..bd9d833 100644 --- a/crates/avalanche-types/src/avm/txs/export.rs +++ b/crates/avalanche-types/src/avm/txs/export.rs @@ -1,10 +1,5 @@ //! Base export transaction type. -use crate::{ - avm::txs::fx, - codec, - errors::{Error, Result}, - hash, ids, key, platformvm, txs, -}; +use crate::{avm::txs::fx, codec, errors::Result, hash, ids, key, txs}; use serde::{Deserialize, Serialize}; /// ref. @@ -74,119 +69,7 @@ impl Tx { packer.pack_bytes(self.destination_chain_id.as_ref())?; // pack the third field in the struct - if self.destination_chain_transferable_outputs.is_some() { - let destination_chain_transferable_outputs = self - .destination_chain_transferable_outputs - .as_ref() - .unwrap(); - packer.pack_u32(destination_chain_transferable_outputs.len() as u32)?; - - for transferable_output in destination_chain_transferable_outputs.iter() { - // "TransferableOutput.Asset" is struct and serialize:"true" - // but embedded inline in the struct "TransferableOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#Asset - packer.pack_bytes(transferable_output.asset_id.as_ref())?; - - // fx_id is serialize:"false" thus skipping serialization - - // decide the type - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - if transferable_output.transfer_output.is_none() - && transferable_output.stakeable_lock_out.is_none() - { - return Err(Error::Other { - message: "unexpected Nones in TransferableOutput transfer_output and stakeable_lock_out".to_string(), - retryable: false, - }); - } - let type_id_transferable_out = { - if transferable_output.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - // marshal type ID for "key::secp256k1::txs::transfer::Output" or "platformvm::txs::StakeableLockOut" - packer.pack_u32(type_id_transferable_out)?; - - match type_id_transferable_out { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output = transferable_output.transfer_output.clone().unwrap(); - - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(transfer_output.amount)?; - - // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" - // but embedded inline in the struct "TransferOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - packer.pack_u64(transfer_output.output_owners.locktime)?; - packer.pack_u32(transfer_output.output_owners.threshold)?; - packer.pack_u32(transfer_output.output_owners.addresses.len() as u32)?; - for addr in transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out = - transferable_output.stakeable_lock_out.clone().unwrap(); - - // marshal "platformvm::txs::StakeableLockOut.locktime" field - packer.pack_u64(stakeable_lock_out.locktime)?; - - // secp256k1fx.TransferOutput type ID - packer.pack_u32(7)?; - - // "platformvm.StakeableLockOut.TransferOutput" is struct and serialize:"true" - // but embedded inline in the struct "StakeableLockOut" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - // - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(stakeable_lock_out.transfer_output.amount)?; - packer - .pack_u64(stakeable_lock_out.transfer_output.output_owners.locktime)?; - packer - .pack_u32(stakeable_lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32( - stakeable_lock_out - .transfer_output - .output_owners - .addresses - .len() as u32, - )?; - for addr in stakeable_lock_out - .transfer_output - .output_owners - .addresses - .iter() - { - packer.pack_bytes(addr.as_ref())?; - } - } - _ => { - return Err(Error::Other { - message: format!( - "unexpected type ID {} for TransferableOutput", - type_id_transferable_out - ), - retryable: false, - }); - } - } - } - } else { - packer.pack_u32(0_u32)?; - } + packer.pack(&self.destination_chain_transferable_outputs)?; // take bytes just for hashing computation let tx_bytes_with_no_signature = packer.take_bytes(); diff --git a/crates/avalanche-types/src/avm/txs/import.rs b/crates/avalanche-types/src/avm/txs/import.rs index 1de5ed6..78eb32e 100644 --- a/crates/avalanche-types/src/avm/txs/import.rs +++ b/crates/avalanche-types/src/avm/txs/import.rs @@ -128,10 +128,7 @@ impl Tx { // so no need to encode type ID // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferInput // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#Input - packer.pack_u32(transfer_input.sig_indices.len() as u32)?; - for idx in transfer_input.sig_indices.iter() { - packer.pack_u32(*idx)?; - } + packer.pack(&transfer_input.sig_indices)?; } 21 => { // "platformvm::txs::StakeableLockIn" @@ -157,11 +154,7 @@ impl Tx { // so no need to encode type ID // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferInput // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#Input - packer - .pack_u32(stakeable_lock_in.transfer_input.sig_indices.len() as u32)?; - for idx in stakeable_lock_in.transfer_input.sig_indices.iter() { - packer.pack_u32(*idx)?; - } + packer.pack(&stakeable_lock_in.transfer_input.sig_indices)?; } _ => { return Err(Error::Other { diff --git a/crates/avalanche-types/src/avm/txs/mod.rs b/crates/avalanche-types/src/avm/txs/mod.rs index 11fe105..54c985a 100644 --- a/crates/avalanche-types/src/avm/txs/mod.rs +++ b/crates/avalanche-types/src/avm/txs/mod.rs @@ -4,7 +4,12 @@ pub mod fx; pub mod import; pub mod vertex; -use crate::{codec, errors::Result, hash, ids, key, txs}; +use crate::{ + codec, + errors::Result, + hash, ids, key, + txs::{self}, +}; use serde::{Deserialize, Serialize}; /// Base transaction. @@ -135,7 +140,7 @@ impl Tx { /// ref. "avalanchego/vms/avm.TestBaseTxSerialization" #[test] fn test_tx_serialization_with_two_signers() { - use crate::ids::short; + use crate::{ids::short, txs::transferable::TransferableOut}; macro_rules! ab { ($e:expr) => { @@ -159,7 +164,7 @@ fn test_tx_serialization_with_two_signers() { blockchain_id: ids::Id::from_slice(&>::from([5, 4, 3, 2, 1])), transferable_outputs: Some(vec![txs::transferable::Output { asset_id: ids::Id::from_slice(&>::from([1, 2, 3])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 12345, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0, diff --git a/crates/avalanche-types/src/ids/short.rs b/crates/avalanche-types/src/ids/short.rs index 8bce604..19299b6 100644 --- a/crates/avalanche-types/src/ids/short.rs +++ b/crates/avalanche-types/src/ids/short.rs @@ -7,7 +7,7 @@ use std::{ str::FromStr, }; -use crate::{formatting, hash, key::secp256k1}; +use crate::{formatting, hash, key::secp256k1, packer::{Packable, Packer}, errors}; use lazy_static::lazy_static; use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned}; @@ -126,6 +126,12 @@ impl<'de> Deserialize<'de> for Id { } } +impl Packable for Id { + fn pack(&self, packer: &Packer) -> errors::Result<()> { + packer.pack_bytes(self.as_ref()) + } +} + fn fmt_id<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/crates/avalanche-types/src/key/secp256k1/txs/transfer.rs b/crates/avalanche-types/src/key/secp256k1/txs/transfer.rs index 9f73b15..569ee5c 100644 --- a/crates/avalanche-types/src/key/secp256k1/txs/transfer.rs +++ b/crates/avalanche-types/src/key/secp256k1/txs/transfer.rs @@ -3,7 +3,12 @@ use std::{ io::{self, Error, ErrorKind}, }; -use crate::{codec, key}; +use crate::{ + codec, + errors::Result, + key, + packer::{Packable, Packer}, +}; use serde::{Deserialize, Serialize}; /// ref. @@ -36,6 +41,29 @@ impl Output { } } +impl Packable for Output { + fn pack(&self, packer: &Packer) -> Result<()> { + // marshal type ID for "key::secp256k1::txs::transfer::Output" + packer.pack_u32(Self::type_id())?; + + // "key::secp256k1::txs::transfer::Output" + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput + + // marshal "secp256k1fx.TransferOutput.Amt" field + packer.pack_u64(self.amount)?; + + // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" + // but embedded inline in the struct "TransferOutput" + // so no need to encode type ID + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners + packer.pack_u64(self.output_owners.locktime)?; + packer.pack_u32(self.output_owners.threshold)?; + packer.pack(&self.output_owners.addresses)?; + Ok(()) + } +} + /// RUST_LOG=debug cargo test --package avalanche-types --lib -- key::secp256k1::txs::transfer::test_transfer_output_custom_de_serializer --exact --show-output #[test] fn test_transfer_output_custom_de_serializer() { diff --git a/crates/avalanche-types/src/packer/mod.rs b/crates/avalanche-types/src/packer/mod.rs index 17f1bfa..04e11a6 100644 --- a/crates/avalanche-types/src/packer/mod.rs +++ b/crates/avalanche-types/src/packer/mod.rs @@ -60,6 +60,17 @@ impl Packer { } } + pub fn new_with_version(codec_version: u16) -> Result { + // ref. "avalanchego/codec.manager.Marshal", "vms/avm.newCustomCodecs" + // ref. "math.MaxInt32" and "constants.DefaultByteSliceCap" in Go + let packer = Self::new((1 << 31) - 1, 128); + + // codec version + // ref. "avalanchego/codec.manager.Marshal" + packer.pack_u16(codec_version)?; + Ok(packer) + } + /// Creates a new Packer with 32-bit message length header. pub fn new_with_header(max_size: usize, initial_cap: usize) -> Self { let mut b = BytesMut::with_capacity(initial_cap); @@ -621,6 +632,53 @@ impl Packer { }; Ok(s) } + + /// Packs a packable type + pub fn pack(&self, v: &T) -> Result<()> { + v.pack(self) + } +} + +/// A trait implemented by types that can be packed using [`Packer`] +pub trait Packable { + fn pack(&self, packer: &Packer) -> Result<()>; +} + +/// [`Packable`] implementation for [`u8`] +impl Packable for u8 { + fn pack(&self, packer: &Packer) -> Result<()> { + packer.pack_byte(*self) + } +} + +/// [`Packable`] implementation for [`u32`] +impl Packable for u32 { + fn pack(&self, packer: &Packer) -> Result<()> { + packer.pack_u32(*self) + } +} + +/// Generic [`Packable`] implementation for [`Option`] +impl Packable for Option { + fn pack(&self, packer: &Packer) -> Result<()> { + if let Some(val) = self { + packer.pack(val)?; + } else { + packer.pack_u32(0_u32)?; + } + Ok(()) + } +} + +/// Generic [`Packable`] implementation for [`Vec`] +impl Packable for Vec { + fn pack(&self, packer: &Packer) -> Result<()> { + packer.pack_u32(self.len() as u32)?; + for val in self { + packer.pack(val)?; + } + Ok(()) + } } /// RUST_LOG=debug cargo test --package avalanche-types --lib -- packer::test_expand --exact --show-output diff --git a/crates/avalanche-types/src/platformvm/txs/add_permissionless_validator.rs b/crates/avalanche-types/src/platformvm/txs/add_permissionless_validator.rs index fbda33d..e6b9140 100644 --- a/crates/avalanche-types/src/platformvm/txs/add_permissionless_validator.rs +++ b/crates/avalanche-types/src/platformvm/txs/add_permissionless_validator.rs @@ -1,7 +1,8 @@ use crate::{ codec, - errors::{Error, Result}, - hash, ids, key, platformvm, txs, + errors::Result, + hash, ids, key, platformvm, + txs::{self}, }; use serde::{Deserialize, Serialize}; @@ -128,115 +129,7 @@ impl Tx { } // pack the third field "stake" in the struct - if let Some(stake_transferable_outputs) = &self.stake_transferable_outputs { - packer.pack_u32(stake_transferable_outputs.len() as u32)?; - - for transferable_output in stake_transferable_outputs.iter() { - // "TransferableOutput.Asset" is struct and serialize:"true" - // but embedded inline in the struct "TransferableOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#Asset - packer.pack_bytes(transferable_output.asset_id.as_ref())?; - - // fx_id is serialize:"false" thus skipping serialization - - // decide the type - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - if transferable_output.transfer_output.is_none() - && transferable_output.stakeable_lock_out.is_none() - { - return Err(Error::Other { - message: "unexpected Nones in TransferableOutput transfer_output and stakeable_lock_out".to_string(), - retryable: false, - }); - } - let type_id_transferable_out = { - if transferable_output.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - // marshal type ID for "key::secp256k1::txs::transfer::Output" or "platformvm::txs::StakeableLockOut" - packer.pack_u32(type_id_transferable_out)?; - - match type_id_transferable_out { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output = transferable_output.transfer_output.clone().unwrap(); - - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(transfer_output.amount)?; - - // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" - // but embedded inline in the struct "TransferOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - packer.pack_u64(transfer_output.output_owners.locktime)?; - packer.pack_u32(transfer_output.output_owners.threshold)?; - packer.pack_u32(transfer_output.output_owners.addresses.len() as u32)?; - for addr in transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out = - transferable_output.stakeable_lock_out.clone().unwrap(); - - // marshal "platformvm::txs::StakeableLockOut.locktime" field - packer.pack_u64(stakeable_lock_out.locktime)?; - - // secp256k1fx.TransferOutput type ID - packer.pack_u32(7)?; - - // "platformvm.StakeableLockOut.TransferOutput" is struct and serialize:"true" - // but embedded inline in the struct "StakeableLockOut" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - // - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(stakeable_lock_out.transfer_output.amount)?; - packer - .pack_u64(stakeable_lock_out.transfer_output.output_owners.locktime)?; - packer - .pack_u32(stakeable_lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32( - stakeable_lock_out - .transfer_output - .output_owners - .addresses - .len() as u32, - )?; - for addr in stakeable_lock_out - .transfer_output - .output_owners - .addresses - .iter() - { - packer.pack_bytes(addr.as_ref())?; - } - } - _ => { - return Err(Error::Other { - message: format!( - "unexpected type ID {} for TransferableOutput", - type_id_transferable_out - ), - retryable: false, - }); - } - } - } - } else { - packer.pack_u32(0_u32)?; - } + packer.pack(&self.stake_transferable_outputs)?; // pack the fourth field "reward_owner" in the struct // not embedded thus encode struct type id @@ -244,18 +137,12 @@ impl Tx { packer.pack_u32(output_owners_type_id)?; packer.pack_u64(self.validator_rewards_owner.locktime)?; packer.pack_u32(self.validator_rewards_owner.threshold)?; - packer.pack_u32(self.validator_rewards_owner.addresses.len() as u32)?; - for addr in self.validator_rewards_owner.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } + packer.pack(&self.validator_rewards_owner.addresses)?; packer.pack_u32(output_owners_type_id)?; packer.pack_u64(self.delegator_rewards_owner.locktime)?; packer.pack_u32(self.delegator_rewards_owner.threshold)?; - packer.pack_u32(self.delegator_rewards_owner.addresses.len() as u32)?; - for addr in self.delegator_rewards_owner.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } + packer.pack(&self.delegator_rewards_owner.addresses)?; // pack the fifth field "shares" in the struct packer.pack_u32(self.delegation_shares)?; @@ -324,7 +211,10 @@ impl Tx { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::add_permissionless_validator::test_add_permissionless_validator_tx_serialization_with_one_signer --exact --show-output #[test] fn test_add_permissionless_validator_tx_serialization_with_one_signer() { - use crate::ids::{node, short}; + use crate::{ + ids::{node, short}, + txs::transferable::TransferableOut, + }; use std::str::FromStr; let _ = env_logger::builder() @@ -353,7 +243,7 @@ fn test_add_permissionless_validator_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2c6874d687fc000, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0, @@ -407,7 +297,7 @@ fn test_add_permissionless_validator_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut{ + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut{ locktime:0, transfer_output: key::secp256k1::txs::transfer::Output { amount: 0x7e7, diff --git a/crates/avalanche-types/src/platformvm/txs/add_subnet_validator.rs b/crates/avalanche-types/src/platformvm/txs/add_subnet_validator.rs index 42e6471..5c4f5d5 100644 --- a/crates/avalanche-types/src/platformvm/txs/add_subnet_validator.rs +++ b/crates/avalanche-types/src/platformvm/txs/add_subnet_validator.rs @@ -88,10 +88,7 @@ impl Tx { // pack the third field "subnet_auth" in the struct let subnet_auth_type_id = key::secp256k1::txs::Input::type_id(); packer.pack_u32(subnet_auth_type_id)?; - packer.pack_u32(self.subnet_auth.sig_indices.len() as u32)?; - for sig_idx in self.subnet_auth.sig_indices.iter() { - packer.pack_u32(*sig_idx)?; - } + packer.pack(&self.subnet_auth.sig_indices)?; // take bytes just for hashing computation let tx_bytes_with_no_signature = packer.take_bytes(); @@ -157,7 +154,10 @@ impl Tx { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::add_subnet_validator::test_add_subnet_validator_tx_serialization_with_one_signer --exact --show-output #[test] fn test_add_subnet_validator_tx_serialization_with_one_signer() { - use crate::ids::{node, short}; + use crate::{ + ids::{node, short}, + txs::transferable::TransferableOut, + }; macro_rules! ab { ($e:expr) => { @@ -175,7 +175,7 @@ fn test_add_subnet_validator_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2c6874d5c56f500, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, diff --git a/crates/avalanche-types/src/platformvm/txs/add_validator.rs b/crates/avalanche-types/src/platformvm/txs/add_validator.rs index b732595..efc155d 100644 --- a/crates/avalanche-types/src/platformvm/txs/add_validator.rs +++ b/crates/avalanche-types/src/platformvm/txs/add_validator.rs @@ -1,7 +1,8 @@ use crate::{ codec, - errors::{Error, Result}, - hash, ids, key, platformvm, txs, + errors::Result, + hash, ids, key, packer, platformvm, + txs::{self}, }; use serde::{Deserialize, Serialize}; @@ -14,13 +15,17 @@ pub struct Tx { /// as long as "avax.BaseTx.Metadata" is "None". /// Once Metadata is updated with signing and "Tx.Initialize", /// Tx.ID() is non-empty. + #[serde(flatten)] pub base_tx: txs::Tx, pub validator: platformvm::txs::Validator, + #[serde(rename = "stake")] pub stake_transferable_outputs: Option>, + #[serde(rename = "rewardsOwner")] pub rewards_owner: key::secp256k1::txs::OutputOwners, pub shares: u32, /// To be updated after signing. + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub creds: Vec, } @@ -58,151 +63,8 @@ impl Tx { &mut self, signers: Vec>, ) -> Result<()> { - // marshal "unsigned tx" with the codec version - let type_id = Self::type_id(); - let packer = self.base_tx.pack(codec::VERSION, type_id)?; - - // "avalanchego" marshals the whole struct again for signed bytes - // even when the underlying "unsigned_tx" is already once marshaled - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#Tx.Sign - // - // reuse the underlying packer to avoid marshaling the unsigned tx twice - // just marshal the next fields in the struct and pack them all together - // in the existing packer - let unsigned_tx_bytes = packer.take_bytes(); - packer.set_bytes(&unsigned_tx_bytes); - - // pack the second field "validator" in the struct - packer.pack_bytes(self.validator.node_id.as_ref())?; - packer.pack_u64(self.validator.start)?; - packer.pack_u64(self.validator.end)?; - packer.pack_u64(self.validator.weight)?; - - // pack the third field "stake" in the struct - if self.stake_transferable_outputs.is_some() { - let stake_transferable_outputs = self.stake_transferable_outputs.as_ref().unwrap(); - packer.pack_u32(stake_transferable_outputs.len() as u32)?; - - for transferable_output in stake_transferable_outputs.iter() { - // "TransferableOutput.Asset" is struct and serialize:"true" - // but embedded inline in the struct "TransferableOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#Asset - packer.pack_bytes(transferable_output.asset_id.as_ref())?; - - // fx_id is serialize:"false" thus skipping serialization - - // decide the type - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - if transferable_output.transfer_output.is_none() - && transferable_output.stakeable_lock_out.is_none() - { - return Err(Error::Other { - message: "unexpected Nones in TransferableOutput transfer_output and stakeable_lock_out".to_string(), - retryable: false, - }); - } - let type_id_transferable_out = { - if transferable_output.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - // marshal type ID for "key::secp256k1::txs::transfer::Output" or "platformvm::txs::StakeableLockOut" - packer.pack_u32(type_id_transferable_out)?; - - match type_id_transferable_out { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output = transferable_output.transfer_output.clone().unwrap(); - - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(transfer_output.amount)?; - - // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" - // but embedded inline in the struct "TransferOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - packer.pack_u64(transfer_output.output_owners.locktime)?; - packer.pack_u32(transfer_output.output_owners.threshold)?; - packer.pack_u32(transfer_output.output_owners.addresses.len() as u32)?; - for addr in transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out = - transferable_output.stakeable_lock_out.clone().unwrap(); - - // marshal "platformvm::txs::StakeableLockOut.locktime" field - packer.pack_u64(stakeable_lock_out.locktime)?; - - // secp256k1fx.TransferOutput type ID - packer.pack_u32(7)?; - - // "platformvm.StakeableLockOut.TransferOutput" is struct and serialize:"true" - // but embedded inline in the struct "StakeableLockOut" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - // - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(stakeable_lock_out.transfer_output.amount)?; - packer - .pack_u64(stakeable_lock_out.transfer_output.output_owners.locktime)?; - packer - .pack_u32(stakeable_lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32( - stakeable_lock_out - .transfer_output - .output_owners - .addresses - .len() as u32, - )?; - for addr in stakeable_lock_out - .transfer_output - .output_owners - .addresses - .iter() - { - packer.pack_bytes(addr.as_ref())?; - } - } - _ => { - return Err(Error::Other { - message: format!( - "unexpected type ID {} for TransferableOutput", - type_id_transferable_out - ), - retryable: false, - }); - } - } - } - } else { - packer.pack_u32(0_u32)?; - } - - // pack the fourth field "reward_owner" in the struct - // not embedded thus encode struct type id - let output_owners_type_id = key::secp256k1::txs::OutputOwners::type_id(); - packer.pack_u32(output_owners_type_id)?; - packer.pack_u64(self.rewards_owner.locktime)?; - packer.pack_u32(self.rewards_owner.threshold)?; - packer.pack_u32(self.rewards_owner.addresses.len() as u32)?; - for addr in self.rewards_owner.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - - // pack the fifth field "shares" in the struct - packer.pack_u32(self.shares)?; + let packer = packer::Packer::new_with_version(codec::VERSION)?; + packer.pack(self)?; // take bytes just for hashing computation let tx_bytes_with_no_signature = packer.take_bytes(); @@ -265,10 +127,43 @@ impl Tx { } } +impl packer::Packable for Tx { + fn pack(&self, packer: &packer::Packer) -> Result<()> { + let type_id = Self::type_id(); + + packer.pack_u32(type_id)?; + packer.pack(&self.base_tx)?; + + // pack the second field "validator" in the struct + packer.pack_bytes(self.validator.node_id.as_ref())?; + packer.pack_u64(self.validator.start)?; + packer.pack_u64(self.validator.end)?; + packer.pack_u64(self.validator.weight)?; + + // pack the third field "stake" in the struct + packer.pack(&self.stake_transferable_outputs)?; + + // pack the fourth field "reward_owner" in the struct + // not embedded thus encode struct type id + let output_owners_type_id = key::secp256k1::txs::OutputOwners::type_id(); + packer.pack_u32(output_owners_type_id)?; + packer.pack_u64(self.rewards_owner.locktime)?; + packer.pack_u32(self.rewards_owner.threshold)?; + packer.pack(&self.rewards_owner.addresses)?; + + // pack the fifth field "shares" in the struct + packer.pack_u32(self.shares)?; + Ok(()) + } +} + /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::add_validator::test_add_validator_tx_serialization_with_one_signer --exact --show-output #[test] fn test_add_validator_tx_serialization_with_one_signer() { - use crate::ids::{node, short}; + use crate::{ + ids::{node, short}, + txs::transferable::TransferableOut, + }; macro_rules! ab { ($e:expr) => { @@ -286,7 +181,7 @@ fn test_add_validator_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2c6874d687fc000, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, @@ -340,7 +235,7 @@ fn test_add_validator_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x1d1a94a2000, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, @@ -545,3 +440,221 @@ fn test_add_validator_tx_serialization_with_one_signer() { &tx_bytes_with_signatures )); } + +/// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::add_validator::test_json_deserialize --exact --show-output +#[test] +fn test_json_deserialize() { + use serde_json::json; + + let tx_json = json!({ + "networkID": 10, + "blockchainID": "11111111111111111111111111111111LpoYY", + "outputs": [ + { + "assetID": "Zda4gsqTjRaX6XVZekVNi3ovMFPHDRQiGbzYuAb7Nwqy1rGBc", + "fxID": "11111111111111111111111111111111LpoYY", + "output": { + "addresses": [ + "Q4MzFZZDPHRPAHFeDs3NiyyaZDvxHKivf" + ], + "amount": 1234, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "tJ4rZfd5dnsPpWPVYU3skNW8uYNpaS6bmpto3sXMMqFMVpR1f", + "outputIndex": 2, + "assetID": "Zda4gsqTjRaX6XVZekVNi3ovMFPHDRQiGbzYuAb7Nwqy1rGBc", + "fxID": "11111111111111111111111111111111LpoYY", + "input": { + "amount": 5678, + "signatureIndices": [ + 0 + ] + } + } + ], + "memo": "0x", + "validator": { + "nodeID": "NodeID-111111111111111111116DBWJs", + "start": 1711696405, + "end": 1711700005, + "weight": 2022 + }, + "stake": [ + { + "assetID": "Zda4gsqTjRaX6XVZekVNi3ovMFPHDRQiGbzYuAb7Nwqy1rGBc", + "fxID": "11111111111111111111111111111111LpoYY", + "output": { + "locktime": 1711696406, + "output": { + "addresses": [ + "Q4MzFZZDPHRPAHFeDs3NiyyaZDvxHKivf" + ], + "amount": 2022, + "locktime": 0, + "threshold": 1 + } + } + } + ], + "rewardsOwner": { + "addresses": [ + "Q4MzFZZDPHRPAHFeDs3NiyyaZDvxHKivf" + ], + "locktime": 0, + "threshold": 1 + }, + "shares": 1000000 + }); + + let tx: Tx = serde_json::from_value(tx_json).expect("parsing tx"); + let packer = packer::Packer::new_with_version(codec::VERSION).expect("creating packer"); + packer.pack(&tx).expect("packing tx"); + + let expected_bytes: &[u8] = &[ + // codec version + 0x00, 0x00, // + // + // AddValidatorTx type ID + 0x00, 0x00, 0x00, 0x0c, // + // + // network id + 0x00, 0x00, 0x00, 0x0a, // + // blockchainID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, // + // + // outputs.len() + 0x00, 0x00, 0x00, 0x01, // + // + // outputs[0].assetID + 0x4a, 0x17, 0x72, 0x05, 0xdf, 0x5c, 0x29, 0x92, 0x9d, 0x06, // + 0xdb, 0x9d, 0x94, 0x1f, 0x83, 0xd5, 0xea, 0x98, 0x5d, 0xe3, // + 0x02, 0x01, 0x5e, 0x99, 0x25, 0x2d, 0x16, 0x46, 0x9a, 0x66, // + 0x10, 0xdb, // + // + // outputs[0] type ID + 0x00, 0x00, 0x00, 0x07, // + // + // outputs[0].output.amount + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xd2, // + // + // outputs[0].output.locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // + // outputs[0].output.threshold + 0x00, 0x00, 0x00, 0x01, // + // + // outputs[0].output.addresses.len() + 0x00, 0x00, 0x00, 0x01, // + // + // outputs[0].output.addresses[0] + 0xfc, 0xed, 0xa8, 0xf9, 0x0f, 0xcb, 0x5d, 0x30, 0x61, 0x4b, // + 0x99, 0xd7, 0x9f, 0xc4, 0xba, 0xa2, 0x93, 0x07, 0x76, 0x26, // + // + // inputs.len() + 0x00, 0x00, 0x00, 0x01, // + // + // inputs[0].txID + 0x74, 0x78, 0x49, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, // + // + // inputs[0].outputIndex + 0x00, 0x00, 0x00, 0x02, // + // + // inputs[0].assetID + 0x4a, 0x17, 0x72, 0x05, 0xdf, 0x5c, 0x29, 0x92, 0x9d, 0x06, // + 0xdb, 0x9d, 0x94, 0x1f, 0x83, 0xd5, 0xea, 0x98, 0x5d, 0xe3, // + 0x02, 0x01, 0x5e, 0x99, 0x25, 0x2d, 0x16, 0x46, 0x9a, 0x66, // + 0x10, 0xdb, // + // + // inputs[0] type ID + 0x00, 0x00, 0x00, 0x05, // + // + // inputs[0].input.amount + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x2e, // + // + // inputs[0].input.signatureIndices.len() + 0x00, 0x00, 0x00, 0x01, // + // + // inputs[0].input.signatureIndices[0] + 0x00, 0x00, 0x00, 0x00, // + // + // memo.len() + 0x00, 0x00, 0x00, 0x00, // + // validator.nodeID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // + // validator.start + 0x00, 0x00, 0x00, 0x00, 0x66, 0x06, 0x6a, 0x15, // + // + // validator.end + 0x00, 0x00, 0x00, 0x00, 0x66, 0x06, 0x78, 0x25, // + // + // validator.weight + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe6, // + // + // stake.len() + 0x00, 0x00, 0x00, 0x01, // + // + // stake[0].assetID + 0x4a, 0x17, 0x72, 0x05, 0xdf, 0x5c, 0x29, 0x92, 0x9d, 0x06, // + 0xdb, 0x9d, 0x94, 0x1f, 0x83, 0xd5, 0xea, 0x98, 0x5d, 0xe3, // + 0x02, 0x01, 0x5e, 0x99, 0x25, 0x2d, 0x16, 0x46, 0x9a, 0x66, // + 0x10, 0xdb, // + // + // stake[0] type ID + 0x00, 0x00, 0x00, 0x16, // + // + // stake[0].locktime + 0x00, 0x00, 0x00, 0x00, 0x66, 0x06, 0x6a, 0x16, // + // + // stake[0].output type ID + 0x00, 0x00, 0x00, 0x07, // + // + // stake[0].output amount + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe6, // + // + // stake[0].output.locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // + // stake[0].output.threshold + 0x00, 0x00, 0x00, 0x01, // + // + // stake[0].output.addresses.len() + 0x00, 0x00, 0x00, 0x01, // + // + // stake[0].output.addresses[0] + 0xfc, 0xed, 0xa8, 0xf9, 0x0f, 0xcb, 0x5d, 0x30, 0x61, 0x4b, // + 0x99, 0xd7, 0x9f, 0xc4, 0xba, 0xa2, 0x93, 0x07, 0x76, 0x26, // + // + // rewardsOwner type ID + 0x00, 0x00, 0x00, 0x0b, // + // + // rewardsOwner.locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // + // rewardsOwner.threshold + 0x00, 0x00, 0x00, 0x01, // + // + // rewardsOwner.addresses.len() + 0x00, 0x00, 0x00, 0x01, // + // + // rewardsOwner.addresses[0] + 0xfc, 0xed, 0xa8, 0xf9, 0x0f, 0xcb, 0x5d, 0x30, 0x61, 0x4b, // + 0x99, 0xd7, 0x9f, 0xc4, 0xba, 0xa2, 0x93, 0x07, 0x76, 0x26, // + // shares + 0x00, 0x0f, 0x42, 0x40, // + ]; + + assert_eq!(packer.take_bytes(), expected_bytes); +} diff --git a/crates/avalanche-types/src/platformvm/txs/create_chain.rs b/crates/avalanche-types/src/platformvm/txs/create_chain.rs index 4ee5237..e1b88f8 100644 --- a/crates/avalanche-types/src/platformvm/txs/create_chain.rs +++ b/crates/avalanche-types/src/platformvm/txs/create_chain.rs @@ -1,4 +1,9 @@ -use crate::{codec, errors::Result, hash, ids, key, txs}; +use crate::{ + codec, + errors::Result, + hash, ids, key, + txs::{self}, +}; use serde::{Deserialize, Serialize}; /// ref. @@ -111,10 +116,7 @@ impl Tx { // pack the seventh field "subnet_auth" in the struct let subnet_auth_type_id = key::secp256k1::txs::Input::type_id(); packer.pack_u32(subnet_auth_type_id)?; - packer.pack_u32(self.subnet_auth.sig_indices.len() as u32)?; - for sig_idx in self.subnet_auth.sig_indices.iter() { - packer.pack_u32(*sig_idx)?; - } + packer.pack(&self.subnet_auth.sig_indices)?; // take bytes just for hashing computation let tx_bytes_with_no_signature = packer.take_bytes(); @@ -180,7 +182,7 @@ impl Tx { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::create_chain::test_create_chain_tx_serialization_with_one_signer --exact --show-output #[test] fn test_create_chain_tx_serialization_with_one_signer() { - use crate::ids::short; + use crate::{ids::short, txs::transferable::TransferableOut}; macro_rules! ab { ($e:expr) => { @@ -198,7 +200,7 @@ fn test_create_chain_tx_serialization_with_one_signer() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2c6874d5c56f500, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, diff --git a/crates/avalanche-types/src/platformvm/txs/create_subnet.rs b/crates/avalanche-types/src/platformvm/txs/create_subnet.rs index c8f9890..7573113 100644 --- a/crates/avalanche-types/src/platformvm/txs/create_subnet.rs +++ b/crates/avalanche-types/src/platformvm/txs/create_subnet.rs @@ -1,4 +1,9 @@ -use crate::{codec, errors::Result, hash, ids, key, txs}; +use crate::{ + codec, + errors::Result, + hash, ids, key, + txs::{self}, +}; use serde::{Deserialize, Serialize}; /// ref. @@ -68,10 +73,7 @@ impl Tx { packer.pack_u32(output_owners_type_id)?; packer.pack_u64(self.owner.locktime)?; packer.pack_u32(self.owner.threshold)?; - packer.pack_u32(self.owner.addresses.len() as u32)?; - for addr in self.owner.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } + packer.pack(&self.owner.addresses)?; // take bytes just for hashing computation let tx_bytes_with_no_signature = packer.take_bytes(); @@ -137,7 +139,7 @@ impl Tx { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::create_subnet::test_create_subnet_tx_serialization_with_one_signer --exact --show-output #[test] fn test_create_subnet_tx_serialization_with_one_signer() { - use crate::ids::short; + use crate::{ids::short, txs::transferable::TransferableOut}; macro_rules! ab { ($e:expr) => { @@ -155,7 +157,7 @@ fn test_create_subnet_tx_serialization_with_one_signer() { 0xd3, 0x84, 0x9b, 0xb9, 0xdf, 0xab, 0xe3, 0x9f, // 0xcb, 0xc3, 0xe7, 0xee, 0x88, 0x11, 0xfe, 0x2f, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2386f269cb1f00, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, @@ -340,7 +342,7 @@ fn test_create_subnet_tx_serialization_with_one_signer() { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::create_subnet::test_create_subnet_tx_serialization_with_custom_network --exact --show-output #[test] fn test_create_subnet_tx_serialization_with_custom_network() { - use crate::ids::short; + use crate::{ids::short, txs::transferable::TransferableOut}; macro_rules! ab { ($e:expr) => { @@ -358,7 +360,7 @@ fn test_create_subnet_tx_serialization_with_custom_network() { 0xe8, 0x5e, 0xa5, 0x74, 0xc7, 0xa1, 0x5a, 0x79, // 0x68, 0x64, 0x4d, 0x14, 0xd5, 0x47, 0x80, 0x14, // ])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 0x2c6874d5c663740, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0x00, diff --git a/crates/avalanche-types/src/platformvm/txs/export.rs b/crates/avalanche-types/src/platformvm/txs/export.rs index 581686b..e8931d3 100644 --- a/crates/avalanche-types/src/platformvm/txs/export.rs +++ b/crates/avalanche-types/src/platformvm/txs/export.rs @@ -1,7 +1,8 @@ use crate::{ codec, - errors::{Error, Result}, - hash, ids, key, platformvm, txs, + errors::Result, + hash, ids, key, + txs::{self}, }; use serde::{Deserialize, Serialize}; @@ -88,98 +89,7 @@ impl Tx { // fx_id is serialize:"false" thus skipping serialization - // decide the type - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - if transferable_output.transfer_output.is_none() - && transferable_output.stakeable_lock_out.is_none() - { - return Err(Error::Other { - message: "unexpected Nones in TransferableOutput transfer_output and stakeable_lock_out".to_string(), - retryable: false, - }); - } - let type_id_transferable_out = { - if transferable_output.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - // marshal type ID for "key::secp256k1::txs::transfer::Output" or "platformvm::txs::StakeableLockOut" - packer.pack_u32(type_id_transferable_out)?; - - match type_id_transferable_out { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output = transferable_output.transfer_output.clone().unwrap(); - - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(transfer_output.amount)?; - - // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" - // but embedded inline in the struct "TransferOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - packer.pack_u64(transfer_output.output_owners.locktime)?; - packer.pack_u32(transfer_output.output_owners.threshold)?; - packer.pack_u32(transfer_output.output_owners.addresses.len() as u32)?; - for addr in transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out = - transferable_output.stakeable_lock_out.clone().unwrap(); - - // marshal "platformvm::txs::StakeableLockOut.locktime" field - packer.pack_u64(stakeable_lock_out.locktime)?; - - // secp256k1fx.TransferOutput type ID - packer.pack_u32(7)?; - - // "platformvm.StakeableLockOut.TransferOutput" is struct and serialize:"true" - // but embedded inline in the struct "StakeableLockOut" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - // - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(stakeable_lock_out.transfer_output.amount)?; - packer - .pack_u64(stakeable_lock_out.transfer_output.output_owners.locktime)?; - packer - .pack_u32(stakeable_lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32( - stakeable_lock_out - .transfer_output - .output_owners - .addresses - .len() as u32, - )?; - for addr in stakeable_lock_out - .transfer_output - .output_owners - .addresses - .iter() - { - packer.pack_bytes(addr.as_ref())?; - } - } - _ => { - return Err(Error::Other { - message: format!( - "unexpected type ID {} for TransferableOutput", - type_id_transferable_out - ), - retryable: false, - }); - } - } + packer.pack(&transferable_output.out)?; } } else { packer.pack_u32(0_u32)?; @@ -250,7 +160,7 @@ impl Tx { /// ref. "avalanchego/vms/platformvm.TestNewExportTx" #[test] fn test_export_tx_serialization_with_one_signer() { - use crate::ids::short; + use crate::{ids::short, txs::transferable::TransferableOut}; macro_rules! ab { ($e:expr) => { @@ -294,7 +204,7 @@ fn test_export_tx_serialization_with_one_signer() { ])), destination_chain_transferable_outputs: Some(vec![txs::transferable::Output { asset_id: ids::Id::from_slice(&>::from([0x79, 0x65, 0x65, 0x74])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 499999900, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0, diff --git a/crates/avalanche-types/src/platformvm/txs/mod.rs b/crates/avalanche-types/src/platformvm/txs/mod.rs index 7eae6a5..f6630a8 100644 --- a/crates/avalanche-types/src/platformvm/txs/mod.rs +++ b/crates/avalanche-types/src/platformvm/txs/mod.rs @@ -13,7 +13,7 @@ use crate::{ codec::{self, serde::hex_0x_bytes::Hex0xBytes}, ids::{self, node}, key, - txs::transferable, + txs::transferable::{self}, }; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -65,6 +65,8 @@ impl Default for UnsignedTx { /// RUST_LOG=debug cargo test --package avalanche-types --lib -- platformvm::txs::test_json_deserialize --exact --show-output #[test] fn test_json_deserialize() { + use crate::txs::transferable::TransferableOut; + let parsed_tx: Tx = serde_json::from_str( " @@ -125,23 +127,16 @@ fn test_json_deserialize() { println!("{:?}", parsed_tx); assert_eq!(parsed_tx.unsigned_tx.network_id, 1000000); - assert_eq!( - parsed_tx.unsigned_tx.transferable_outputs.clone().unwrap()[0] - .transfer_output - .clone() - .unwrap() - .amount, - 245952587549460688 - ); - assert_eq!( - parsed_tx.unsigned_tx.transferable_outputs.clone().unwrap()[0] - .transfer_output - .clone() - .unwrap() - .output_owners - .threshold, - 1 - ); + match &parsed_tx.unsigned_tx.transferable_outputs.unwrap()[0].out { + TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { + amount, + output_owners, + }) => { + assert_eq!(*amount, 245952587549460688 as u64); + assert_eq!(output_owners.threshold, 1); + } + _ => panic!("unexpected transferable output"), + } } /// ref. @@ -259,6 +254,7 @@ fn test_sort_stakeable_lock_ins() { #[derive(Debug, Serialize, Deserialize, Eq, Clone, Default)] pub struct StakeableLockOut { pub locktime: u64, + #[serde(rename = "output")] pub transfer_output: key::secp256k1::txs::transfer::Output, } @@ -460,6 +456,7 @@ fn test_sort_stakeable_lock_outs() { /// ref. #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] pub struct Validator { + #[serde(rename = "nodeID")] pub node_id: node::Id, pub start: u64, pub end: u64, diff --git a/crates/avalanche-types/src/txs/mod.rs b/crates/avalanche-types/src/txs/mod.rs index f52b0cf..06978ab 100644 --- a/crates/avalanche-types/src/txs/mod.rs +++ b/crates/avalanche-types/src/txs/mod.rs @@ -83,125 +83,20 @@ impl Tx { // ref. "avalanchego/codec.manager.Marshal" packer.pack_u16(codec_version)?; packer.pack_u32(type_id)?; + packer.pack(self)?; + Ok(packer) + } +} +impl packer::Packable for Tx { + fn pack(&self, packer: &packer::Packer) -> Result<()> { // marshal the actual struct "avm.BaseTx" // "BaseTx.Metadata" is not serialize:"true" thus skipping serialization!!! // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#BaseTx // ref. "avalanchego/codec/reflectcodec.structFielder" packer.pack_u32(self.network_id)?; packer.pack_bytes(self.blockchain_id.as_ref())?; - - // "transferable_outputs" field; pack the number of slice elements - if self.transferable_outputs.is_some() { - let transferable_outputs = self.transferable_outputs.as_ref().unwrap(); - packer.pack_u32(transferable_outputs.len() as u32)?; - - for transferable_output in transferable_outputs.iter() { - // "TransferableOutput.Asset" is struct and serialize:"true" - // but embedded inline in the struct "TransferableOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#Asset - packer.pack_bytes(transferable_output.asset_id.as_ref())?; - - // fx_id is serialize:"false" thus skipping serialization - - // decide the type - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput - if transferable_output.transfer_output.is_none() - && transferable_output.stakeable_lock_out.is_none() - { - return Err(Error::Other { - message: "unexpected Nones in TransferableOutput transfer_output and stakeable_lock_out".to_string(), - retryable: false, - }); - } - let type_id_transferable_out = { - if transferable_output.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - // marshal type ID for "key::secp256k1::txs::transfer::Output" or "platformvm::txs::StakeableLockOut" - packer.pack_u32(type_id_transferable_out)?; - - match type_id_transferable_out { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output = transferable_output.transfer_output.clone().unwrap(); - - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(transfer_output.amount)?; - - // "secp256k1fx.TransferOutput.OutputOwners" is struct and serialize:"true" - // but embedded inline in the struct "TransferOutput" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - packer.pack_u64(transfer_output.output_owners.locktime)?; - packer.pack_u32(transfer_output.output_owners.threshold)?; - packer.pack_u32(transfer_output.output_owners.addresses.len() as u32)?; - for addr in transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out = - transferable_output.stakeable_lock_out.clone().unwrap(); - - // marshal "platformvm::txs::StakeableLockOut.locktime" field - packer.pack_u64(stakeable_lock_out.locktime)?; - - // secp256k1fx.TransferOutput type ID - packer.pack_u32(7)?; - - // "platformvm.StakeableLockOut.TransferOutput" is struct and serialize:"true" - // but embedded inline in the struct "StakeableLockOut" - // so no need to encode type ID - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#OutputOwners - // - // marshal "secp256k1fx.TransferOutput.Amt" field - packer.pack_u64(stakeable_lock_out.transfer_output.amount)?; - packer - .pack_u64(stakeable_lock_out.transfer_output.output_owners.locktime)?; - packer - .pack_u32(stakeable_lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32( - stakeable_lock_out - .transfer_output - .output_owners - .addresses - .len() as u32, - )?; - for addr in stakeable_lock_out - .transfer_output - .output_owners - .addresses - .iter() - { - packer.pack_bytes(addr.as_ref())?; - } - } - _ => { - return Err(Error::Other { - message: format!( - "unexpected type ID {} for TransferableOutput", - type_id_transferable_out - ), - retryable: false, - }) - } - } - } - } else { - packer.pack_u32(0_u32)?; - } + packer.pack(&self.transferable_outputs)?; // "transferable_inputs" field; pack the number of slice elements if self.transferable_inputs.is_some() { @@ -260,10 +155,7 @@ impl Tx { // so no need to encode type ID // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferInput // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#Input - packer.pack_u32(transfer_input.sig_indices.len() as u32)?; - for idx in transfer_input.sig_indices.iter() { - packer.pack_u32(*idx)?; - } + packer.pack(&transfer_input.sig_indices)?; } 21 => { // "platformvm::txs::StakeableLockIn" @@ -289,11 +181,7 @@ impl Tx { // so no need to encode type ID // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferInput // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#Input - packer - .pack_u32(stakeable_lock_in.transfer_input.sig_indices.len() as u32)?; - for idx in stakeable_lock_in.transfer_input.sig_indices.iter() { - packer.pack_u32(*idx)?; - } + packer.pack(&stakeable_lock_in.transfer_input.sig_indices)?; } _ => { return Err(Error::Other { @@ -310,16 +198,8 @@ impl Tx { packer.pack_u32(0_u32)?; } - // marshal "BaseTx.memo" - if self.memo.is_some() { - let memo = self.memo.as_ref().unwrap(); - packer.pack_u32(memo.len() as u32)?; - packer.pack_bytes(memo)?; - } else { - packer.pack_u32(0_u32)?; - } - - Ok(packer) + packer.pack(&self.memo)?; + Ok(()) } } @@ -327,7 +207,7 @@ impl Tx { /// ref. "avalanchego/vms/avm.TestBaseTxSerialization" #[test] fn test_base_tx_serialization() { - use crate::{ids::short, key}; + use crate::{ids::short, key, txs::transferable::TransferableOut}; // ref. "avalanchego/vms/avm/vm_test.go" let test_key = key::secp256k1::private_key::Key::from_cb58( @@ -345,7 +225,7 @@ fn test_base_tx_serialization() { blockchain_id: ids::Id::from_slice(&>::from([5, 4, 3, 2, 1])), transferable_outputs: Some(vec![transferable::Output { asset_id: ids::Id::from_slice(&>::from([1, 2, 3])), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: 12345, output_owners: key::secp256k1::txs::OutputOwners { locktime: 0, diff --git a/crates/avalanche-types/src/txs/transferable.rs b/crates/avalanche-types/src/txs/transferable.rs index d966424..72b6d41 100644 --- a/crates/avalanche-types/src/txs/transferable.rs +++ b/crates/avalanche-types/src/txs/transferable.rs @@ -1,8 +1,86 @@ use std::cmp::Ordering; -use crate::{ids, key, platformvm, txs}; +use crate::{ + errors::Result, + ids, key, + packer::{Packable, Packer}, + platformvm, txs, +}; use serde::{Deserialize, Serialize}; +/// Implementation of "*components.avax.TransferOut" +/// ref. +/// which is either: +/// +/// "*secp256k1fx.TransferOutput" +/// ref. +/// +/// "*platformvm.StakeableLockOut" which embeds "*secp256k1fx.TransferOutput" +/// ref. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, PartialOrd)] +#[serde(untagged)] +pub enum TransferableOut { + TransferOutput(key::secp256k1::txs::transfer::Output), + StakeableLockOut(platformvm::txs::StakeableLockOut), +} + +impl TransferableOut { + pub fn type_id(&self) -> u32 { + match self { + TransferableOut::TransferOutput(_out) => { + key::secp256k1::txs::transfer::Output::type_id() + } + TransferableOut::StakeableLockOut(_out) => platformvm::txs::StakeableLockOut::type_id(), + } + } +} + +impl Packable for TransferableOut { + fn pack(&self, packer: &Packer) -> Result<()> { + match self { + TransferableOut::TransferOutput(transfer_output) => { + packer.pack(transfer_output)?; + } + TransferableOut::StakeableLockOut(stakeable_lock_out) => { + // marshal type ID "platformvm::txs::StakeableLockOut" + packer.pack_u32(platformvm::txs::StakeableLockOut::type_id())?; + + // "platformvm::txs::StakeableLockOut" + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut + + // marshal "platformvm::txs::StakeableLockOut.locktime" field + packer.pack_u64(stakeable_lock_out.locktime)?; + packer.pack(&stakeable_lock_out.transfer_output)?; + } + } + Ok(()) + } +} + +impl Ord for TransferableOut { + fn cmp(&self, other: &TransferableOut) -> Ordering { + // unlike "avalanchego", we want ordering without "codec" dependency and marshal-ing + // just check type ID header + let type_id_ord = self.type_id().cmp(&other.type_id()); + if type_id_ord != Ordering::Equal { + // no need to compare further + return type_id_ord; + } + + match (self, other) { + ( + TransferableOut::TransferOutput(out_self), + TransferableOut::TransferOutput(out_other), + ) => out_self.cmp(out_other), + ( + TransferableOut::StakeableLockOut(out_self), + TransferableOut::StakeableLockOut(out_other), + ) => out_self.cmp(out_other), + (_, _) => self.type_id().cmp(&other.type_id()), + } + } +} + /// ref. /// ref. /// ref. @@ -15,18 +93,8 @@ pub struct Output { #[serde(rename = "fxID", skip_serializing_if = "Option::is_none")] pub fx_id: Option, - /// The underlying type is one of the following: - /// - /// "*secp256k1fx.TransferOutput" - /// ref. - /// - /// "*platformvm.StakeableLockOut" which embeds "*secp256k1fx.TransferOutput" - /// ref. - /// - /// MUST: only one of the following can be "Some". #[serde(rename = "output")] - pub transfer_output: Option, - pub stakeable_lock_out: Option, + pub out: TransferableOut, } impl Default for Output { @@ -34,8 +102,7 @@ impl Default for Output { Self { asset_id: ids::Id::empty(), fx_id: None, - transfer_output: None, - stakeable_lock_out: None, + out: TransferableOut::TransferOutput(Default::default()), } } } @@ -48,60 +115,8 @@ impl Ord for Output { // no need to compare further return asset_id_ord; } - if self.transfer_output.is_none() - && self.stakeable_lock_out.is_none() - && other.transfer_output.is_none() - && other.stakeable_lock_out.is_none() - { - // should never happen but sorting/ordering shouldn't worry about this... - // can't compare anymore, so thus return here... - return Ordering::Equal; - } - // unlike "avalanchego", we want ordering without "codec" dependency and marshal-ing - // just check type ID header - let type_id_self = { - if self.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - let type_id_other = { - if other.transfer_output.is_some() { - key::secp256k1::txs::transfer::Output::type_id() - } else { - platformvm::txs::StakeableLockOut::type_id() - } - }; - let type_id_ord = type_id_self.cmp(&type_id_other); - if type_id_ord != Ordering::Equal { - // no need to compare further - return type_id_ord; - } - - // both instances have the same type!!! - // just use the ordering of underlying types - match type_id_self { - 7 => { - // "key::secp256k1::txs::transfer::Output" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput - let transfer_output_self = self.transfer_output.clone().unwrap(); - let transfer_output_other = other.transfer_output.clone().unwrap(); - transfer_output_self.cmp(&transfer_output_other) - } - 22 => { - // "platformvm::txs::StakeableLockOut" - // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/platformvm#StakeableLockOut - let stakeable_lock_out_self = self.stakeable_lock_out.clone().unwrap(); - let stakeable_lock_out_other = other.stakeable_lock_out.clone().unwrap(); - stakeable_lock_out_self.cmp(&stakeable_lock_out_other) - } - _ => { - // should never happen - panic!("unexpected type ID {} for TransferableOutput", type_id_self); - } - } + self.out.cmp(&other.out) } } @@ -117,6 +132,19 @@ impl PartialEq for Output { } } +impl Packable for Output { + fn pack(&self, packer: &Packer) -> Result<()> { + // "TransferableOutput.Asset" is struct and serialize:"true" + // but embedded inline in the struct "TransferableOutput" + // so no need to encode type ID + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput + // ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#Asset + packer.pack_bytes(self.asset_id.as_ref())?; + packer.pack(&self.out)?; + Ok(()) + } +} + /// ref. /// ref. "avalanchego/vms/components/avax.TestTransferableOutputSorting" /// RUST_LOG=debug cargo test --package avalanche-types --lib -- txs::transferable::test_sort_transferable_outputs --exact --show-output @@ -128,7 +156,7 @@ fn test_sort_transferable_outputs() { for i in (0..10).rev() { outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -146,7 +174,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -164,7 +192,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -179,7 +207,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: i as u64, @@ -194,7 +222,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: i as u64, output_owners: key::secp256k1::txs::OutputOwners { locktime: i as u64, @@ -206,7 +234,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -221,7 +249,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: i as u64, @@ -236,7 +264,7 @@ fn test_sort_transferable_outputs() { }); outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: i as u64, output_owners: key::secp256k1::txs::OutputOwners { locktime: i as u64, @@ -254,7 +282,7 @@ fn test_sort_transferable_outputs() { for i in 0..10 { sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: i as u64, output_owners: key::secp256k1::txs::OutputOwners { locktime: i as u64, @@ -266,7 +294,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: i as u64, @@ -281,7 +309,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -296,7 +324,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - transfer_output: Some(key::secp256k1::txs::transfer::Output { + out: TransferableOut::TransferOutput(key::secp256k1::txs::transfer::Output { amount: i as u64, output_owners: key::secp256k1::txs::OutputOwners { locktime: i as u64, @@ -308,7 +336,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: i as u64, @@ -323,7 +351,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -338,7 +366,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, @@ -356,7 +384,7 @@ fn test_sort_transferable_outputs() { }); sorted_outputs.push(Output { asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]), - stakeable_lock_out: Some(platformvm::txs::StakeableLockOut { + out: TransferableOut::StakeableLockOut(platformvm::txs::StakeableLockOut { locktime: i as u64, transfer_output: key::secp256k1::txs::transfer::Output { amount: (i + 1) as u64, diff --git a/crates/avalanche-types/src/txs/utxo.rs b/crates/avalanche-types/src/txs/utxo.rs index 9ca70be..7b38d48 100644 --- a/crates/avalanche-types/src/txs/utxo.rs +++ b/crates/avalanche-types/src/txs/utxo.rs @@ -131,7 +131,9 @@ fn test_utxo_id() { /// TODO: implement ordering? #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Utxo { + #[serde(flatten)] pub utxo_id: Id, + #[serde(rename = "assetID")] pub asset_id: ids::Id, /// AvalancheGo loads "avax.UTXO" object from the db and @@ -206,10 +208,7 @@ impl Utxo { packer.pack_u64(out.output_owners.locktime)?; packer.pack_u32(out.output_owners.threshold)?; - packer.pack_u32(out.output_owners.addresses.len() as u32)?; - for addr in out.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } + packer.pack(&out.output_owners.addresses)?; } else if let Some(lock_out) = &self.stakeable_lock_out { packer.pack_u32(platformvm::txs::StakeableLockOut::type_id())?; packer.pack_u64(lock_out.locktime)?; @@ -220,10 +219,7 @@ impl Utxo { packer.pack_u64(lock_out.transfer_output.output_owners.locktime)?; packer.pack_u32(lock_out.transfer_output.output_owners.threshold)?; - packer.pack_u32(lock_out.transfer_output.output_owners.addresses.len() as u32)?; - for addr in lock_out.transfer_output.output_owners.addresses.iter() { - packer.pack_bytes(addr.as_ref())?; - } + packer.pack(&lock_out.transfer_output.output_owners.addresses)?; } Ok(packer)