use alloc::vec::Vec;
use core::fmt::{self, Debug, Display, Formatter};
use super::{
serialization::CalltableSerializationEnvelope, TransactionInvocationTarget, TransactionRuntime,
};
#[cfg(any(feature = "testing", test))]
use crate::testing::TestRng;
use crate::{
bytesrepr::{
Bytes,
Error::{self, Formatting},
FromBytes, ToBytes,
},
transaction::serialization::CalltableSerializationEnvelopeBuilder,
HashAddr,
};
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(any(feature = "testing", test))]
use rand::{Rng, RngCore};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
feature = "json-schema",
derive(JsonSchema),
schemars(description = "Execution target of a Transaction.")
)]
#[serde(deny_unknown_fields)]
pub enum TransactionTarget {
Native,
Stored {
id: TransactionInvocationTarget,
runtime: TransactionRuntime,
transferred_value: u64,
},
Session {
is_install_upgrade: bool,
runtime: TransactionRuntime,
module_bytes: Bytes,
transferred_value: u64,
seed: Option<[u8; 32]>,
},
}
impl TransactionTarget {
pub fn new_native() -> Self {
TransactionTarget::Native
}
pub fn new_stored(
id: TransactionInvocationTarget,
runtime: TransactionRuntime,
transferred_value: u64,
) -> Self {
TransactionTarget::Stored {
id,
runtime,
transferred_value,
}
}
pub fn new_session(
is_install_upgrade: bool,
module_bytes: Bytes,
runtime: TransactionRuntime,
transferred_value: u64,
seed: Option<[u8; 32]>,
) -> Self {
TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
transferred_value,
seed,
}
}
fn serialized_field_lengths(&self) -> Vec<usize> {
match self {
TransactionTarget::Native => {
vec![crate::bytesrepr::U8_SERIALIZED_LENGTH]
}
TransactionTarget::Stored {
id,
runtime,
transferred_value,
} => {
vec![
crate::bytesrepr::U8_SERIALIZED_LENGTH,
id.serialized_length(),
runtime.serialized_length(),
transferred_value.serialized_length(),
]
}
TransactionTarget::Session {
is_install_upgrade,
runtime,
transferred_value,
seed,
module_bytes,
} => {
vec![
crate::bytesrepr::U8_SERIALIZED_LENGTH,
is_install_upgrade.serialized_length(),
runtime.serialized_length(),
module_bytes.serialized_length(),
transferred_value.serialized_length(),
seed.serialized_length(),
]
}
}
}
pub fn contract_hash_addr(&self) -> Option<HashAddr> {
if let Some(invocation_target) = self.invocation_target() {
invocation_target.contract_by_hash()
} else {
None
}
}
pub fn invocation_target(&self) -> Option<TransactionInvocationTarget> {
match self {
TransactionTarget::Native | TransactionTarget::Session { .. } => None,
TransactionTarget::Stored { id, .. } => Some(id.clone()),
}
}
#[cfg(any(feature = "testing", test))]
pub fn random(rng: &mut TestRng) -> Self {
match rng.gen_range(0..3) {
0 => TransactionTarget::Native,
1 => TransactionTarget::new_stored(
TransactionInvocationTarget::random(rng),
TransactionRuntime::VmCasperV1,
rng.gen(),
),
2 => {
let mut buffer = vec![0u8; rng.gen_range(0..100)];
rng.fill_bytes(buffer.as_mut());
let is_install_upgrade = rng.gen();
TransactionTarget::new_session(
is_install_upgrade,
Bytes::from(buffer),
TransactionRuntime::VmCasperV1,
rng.gen(),
None,
)
}
_ => unreachable!(),
}
}
#[must_use]
pub fn is_session(&self) -> bool {
matches!(self, Self::Session { .. })
}
}
const TAG_FIELD_INDEX: u16 = 0;
const NATIVE_VARIANT: u8 = 0;
const STORED_VARIANT: u8 = 1;
const STORED_ID_INDEX: u16 = 1;
const STORED_RUNTIME_INDEX: u16 = 2;
const STORED_TRANSFERRED_VALUE_INDEX: u16 = 3;
const SESSION_VARIANT: u8 = 2;
const SESSION_IS_INSTALL_INDEX: u16 = 1;
const SESSION_RUNTIME_INDEX: u16 = 2;
const SESSION_MODULE_BYTES_INDEX: u16 = 3;
const SESSION_TRANSFERRED_VALUE_INDEX: u16 = 4;
const SESSION_SEED_INDEX: u16 = 5;
impl ToBytes for TransactionTarget {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
match self {
TransactionTarget::Native => {
CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
.add_field(TAG_FIELD_INDEX, &NATIVE_VARIANT)?
.binary_payload_bytes()
}
TransactionTarget::Stored {
id,
runtime,
transferred_value,
} => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
.add_field(TAG_FIELD_INDEX, &STORED_VARIANT)?
.add_field(STORED_ID_INDEX, &id)?
.add_field(STORED_RUNTIME_INDEX, &runtime)?
.add_field(STORED_TRANSFERRED_VALUE_INDEX, transferred_value)?
.binary_payload_bytes(),
TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
transferred_value,
seed,
} => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
.add_field(TAG_FIELD_INDEX, &SESSION_VARIANT)?
.add_field(SESSION_IS_INSTALL_INDEX, &is_install_upgrade)?
.add_field(SESSION_RUNTIME_INDEX, &runtime)?
.add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)?
.add_field(SESSION_TRANSFERRED_VALUE_INDEX, transferred_value)?
.add_field(SESSION_SEED_INDEX, seed)?
.binary_payload_bytes(),
}
}
fn serialized_length(&self) -> usize {
CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths())
}
}
impl FromBytes for TransactionTarget {
fn from_bytes(bytes: &[u8]) -> Result<(TransactionTarget, &[u8]), Error> {
let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(6, bytes)?;
let window = binary_payload.start_consuming()?.ok_or(Formatting)?;
window.verify_index(TAG_FIELD_INDEX)?;
let (tag, window) = window.deserialize_and_maybe_next::<u8>()?;
let to_ret = match tag {
NATIVE_VARIANT => {
if window.is_some() {
return Err(Formatting);
}
Ok(TransactionTarget::Native)
}
STORED_VARIANT => {
let window = window.ok_or(Formatting)?;
window.verify_index(STORED_ID_INDEX)?;
let (id, window) =
window.deserialize_and_maybe_next::<TransactionInvocationTarget>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(STORED_RUNTIME_INDEX)?;
let (runtime, window) =
window.deserialize_and_maybe_next::<TransactionRuntime>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(STORED_TRANSFERRED_VALUE_INDEX)?;
let (transferred_value, window) = window.deserialize_and_maybe_next::<u64>()?;
if window.is_some() {
return Err(Formatting);
}
Ok(TransactionTarget::Stored {
id,
runtime,
transferred_value,
})
}
SESSION_VARIANT => {
let window = window.ok_or(Formatting)?;
window.verify_index(SESSION_IS_INSTALL_INDEX)?;
let (is_install_upgrade, window) = window.deserialize_and_maybe_next::<bool>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(SESSION_RUNTIME_INDEX)?;
let (runtime, window) =
window.deserialize_and_maybe_next::<TransactionRuntime>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(SESSION_MODULE_BYTES_INDEX)?;
let (module_bytes, window) = window.deserialize_and_maybe_next::<Bytes>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(SESSION_TRANSFERRED_VALUE_INDEX)?;
let (transferred_value, window) = window.deserialize_and_maybe_next::<u64>()?;
let window = window.ok_or(Formatting)?;
window.verify_index(SESSION_SEED_INDEX)?;
let (seed, window) = window.deserialize_and_maybe_next::<Option<[u8; 32]>>()?;
if window.is_some() {
return Err(Formatting);
}
Ok(TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
transferred_value,
seed,
})
}
_ => Err(Formatting),
};
to_ret.map(|endpoint| (endpoint, remainder))
}
}
impl Display for TransactionTarget {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match self {
TransactionTarget::Native => write!(formatter, "native"),
TransactionTarget::Stored {
id,
runtime,
transferred_value,
} => {
write!(
formatter,
"stored({}, {}, {})",
id, runtime, transferred_value
)
}
TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
transferred_value,
seed,
} => write!(
formatter,
"session({} module bytes, runtime: {}, is_install_upgrade: {}, transferred_value: {}, seed: {:?})",
module_bytes.len(),
runtime,
is_install_upgrade,
transferred_value,
seed,
),
}
}
}
impl Debug for TransactionTarget {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
TransactionTarget::Native => formatter.debug_struct("Native").finish(),
TransactionTarget::Stored {
id,
runtime,
transferred_value,
} => formatter
.debug_struct("Stored")
.field("id", id)
.field("runtime", runtime)
.field("transferred_value", transferred_value)
.finish(),
TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
transferred_value,
seed,
} => {
struct BytesLen(usize);
impl Debug for BytesLen {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "{} bytes", self.0)
}
}
formatter
.debug_struct("Session")
.field("module_bytes", &BytesLen(module_bytes.len()))
.field("runtime", runtime)
.field("is_install_upgrade", is_install_upgrade)
.field("transferred_value", transferred_value)
.field("seed", seed)
.finish()
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{bytesrepr, gens::transaction_target_arb};
use proptest::prelude::*;
proptest! {
#[test]
fn generative_bytesrepr_roundtrip(val in transaction_target_arb()) {
bytesrepr::test_serialization_roundtrip(&val);
}
}
}