use crate::address::AddressSerializer;
use crate::datastore::{Datastore, DatastoreDeserializer, DatastoreSerializer};
use crate::prehash::{PreHashSet, PreHashed};
use crate::secure_share::{
Id, SecureShare, SecureShareContent, SecureShareDeserializer, SecureShareSerializer,
};
use crate::{
address::{Address, AddressDeserializer},
amount::{Amount, AmountDeserializer, AmountSerializer},
error::ModelsError,
serialization::{StringDeserializer, StringSerializer, VecU8Deserializer, VecU8Serializer},
};
use massa_hash::{Hash, HashDeserializer};
use massa_serialization::{
DeserializeError, Deserializer, SerializeError, Serializer, U16VarIntDeserializer,
U16VarIntSerializer, U32VarIntDeserializer, U32VarIntSerializer, U64VarIntDeserializer,
U64VarIntSerializer,
};
use massa_signature::PublicKey;
use nom::error::{context, ErrorKind};
use nom::multi::length_count;
use nom::sequence::tuple;
use nom::AsBytes;
use nom::Parser;
use nom::{
error::{ContextError, ParseError},
IResult,
};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DeserializeFromStr, SerializeDisplay};
use std::convert::TryInto;
use std::fmt::Formatter;
use std::{ops::Bound::Included, ops::RangeInclusive, str::FromStr};
use transition::Versioned;
pub const OPERATION_ID_PREFIX_SIZE_BYTES: usize = 17;
#[allow(missing_docs)]
#[transition::versioned(versions("0"))]
#[derive(
Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, SerializeDisplay, DeserializeFromStr,
)]
pub struct OperationId(Hash);
const OPERATIONID_PREFIX: char = 'O';
#[allow(missing_docs)]
#[transition::versioned(versions("0"))]
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[allow(unused_macros)]
pub struct OperationPrefixId([u8; OPERATION_ID_PREFIX_SIZE_BYTES]);
impl std::fmt::Display for OperationPrefixId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperationPrefixId::OperationPrefixIdV0(prefix) => write!(f, "{}", prefix),
}
}
}
#[transition::impl_version(versions("0"))]
impl std::fmt::Display for OperationPrefixId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let u64_serializer = U64VarIntSerializer::new();
let mut bytes: Vec<u8> = Vec::new();
u64_serializer
.serialize(&Self::VERSION, &mut bytes)
.map_err(|_| std::fmt::Error)?;
bytes.extend(self.0.as_bytes());
write!(f, "{}", bs58::encode(bytes).into_string())
}
}
impl std::fmt::Debug for OperationPrefixId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperationPrefixId::OperationPrefixIdV0(prefix) => write!(f, "{:?}", prefix),
}
}
}
#[transition::impl_version(versions("0"))]
impl std::fmt::Debug for OperationPrefixId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl std::fmt::Display for OperationId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperationId::OperationIdV0(id) => write!(f, "{}", id),
}
}
}
#[transition::impl_version(versions("0"))]
impl std::fmt::Display for OperationId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let u64_serializer = U64VarIntSerializer::new();
let mut bytes: Vec<u8> = Vec::new();
u64_serializer
.serialize(&Self::VERSION, &mut bytes)
.map_err(|_| std::fmt::Error)?;
bytes.extend(self.0.to_bytes());
write!(
f,
"{}{}",
OPERATIONID_PREFIX,
bs58::encode(bytes).with_check().into_string()
)
}
}
impl std::fmt::Debug for OperationId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperationId::OperationIdV0(id) => write!(f, "{:?}", id),
}
}
}
#[transition::impl_version(versions("0"))]
impl std::fmt::Debug for OperationId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl FromStr for OperationId {
type Err = ModelsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
match chars.next() {
Some(prefix) if prefix == OPERATIONID_PREFIX => {
let data = chars.collect::<String>();
let decoded_bs58_check = bs58::decode(data)
.with_check(None)
.into_vec()
.map_err(|_| ModelsError::OperationIdParseError)?;
let operation_id_deserializer = OperationIdDeserializer::new();
let (rest, op_id) = operation_id_deserializer
.deserialize::<DeserializeError>(&decoded_bs58_check[..])
.map_err(|_| ModelsError::OperationIdParseError)?;
if rest.is_empty() {
Ok(op_id)
} else {
Err(ModelsError::OperationIdParseError)
}
}
_ => Err(ModelsError::OperationIdParseError),
}
}
}
#[transition::impl_version(versions("0"))]
impl FromStr for OperationId {
type Err = ModelsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
match chars.next() {
Some(prefix) if prefix == OPERATIONID_PREFIX => {
let data = chars.collect::<String>();
let decoded_bs58_check = bs58::decode(data)
.with_check(None)
.into_vec()
.map_err(|_| ModelsError::OperationIdParseError)?;
let operation_id_deserializer = OperationIdDeserializer::new();
let (rest, op_id) = operation_id_deserializer
.deserialize::<DeserializeError>(&decoded_bs58_check[..])
.map_err(|_| ModelsError::OperationIdParseError)?;
if rest.is_empty() {
Ok(op_id)
} else {
Err(ModelsError::OperationIdParseError)
}
}
_ => Err(ModelsError::OperationIdParseError),
}
}
}
impl PreHashed for OperationId {}
impl Id for OperationId {
fn new(hash: Hash) -> Self {
OperationId::OperationIdV0(OperationIdV0(hash))
}
fn get_hash(&self) -> &Hash {
match self {
OperationId::OperationIdV0(op_id) => op_id.get_hash(),
}
}
}
impl PreHashed for OperationPrefixId {}
impl From<&[u8; OPERATION_ID_PREFIX_SIZE_BYTES]> for OperationPrefixId {
fn from(bytes: &[u8; OPERATION_ID_PREFIX_SIZE_BYTES]) -> Self {
OperationPrefixIdVariant!["0"](OperationPrefixId!["0"](*bytes))
}
}
impl From<&OperationPrefixId> for Vec<u8> {
fn from(prefix: &OperationPrefixId) -> Self {
match prefix {
OperationPrefixId::OperationPrefixIdV0(prefix) => prefix.0.to_vec(),
}
}
}
impl OperationId {
pub fn into_prefix(self) -> OperationPrefixId {
match self {
OperationId::OperationIdV0(op_id) => op_id.into_prefix(),
}
}
pub fn prefix(&self) -> OperationPrefixId {
match self {
OperationId::OperationIdV0(op_id) => op_id.prefix(),
}
}
pub fn get_version(&self) -> u64 {
match self {
OperationId::OperationIdV0(op_id) => op_id.get_version(),
}
}
}
#[transition::impl_version(versions("0"))]
impl OperationId {
pub fn into_prefix(self) -> OperationPrefixId {
OperationPrefixId::OperationPrefixIdV0(OperationPrefixIdV0(
self.0.into_bytes()[..OPERATION_ID_PREFIX_SIZE_BYTES]
.try_into()
.expect("failed to truncate prefix from OperationId"),
))
}
pub fn prefix(&self) -> OperationPrefixId {
OperationPrefixId::OperationPrefixIdV0(OperationPrefixIdV0(
self.0.to_bytes()[..OPERATION_ID_PREFIX_SIZE_BYTES]
.try_into()
.expect("failed to truncate prefix from OperationId"),
))
}
fn get_hash(&self) -> &Hash {
&self.0
}
fn get_version(&self) -> u64 {
Self::VERSION
}
}
#[derive(Default, Clone)]
pub struct OperationIdSerializer {
version_serializer: U64VarIntSerializer,
}
impl OperationIdSerializer {
pub fn new() -> Self {
Self {
version_serializer: U64VarIntSerializer::new(),
}
}
}
impl Serializer<OperationId> for OperationIdSerializer {
fn serialize(&self, value: &OperationId, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
self.version_serializer
.serialize(&value.get_version(), buffer)?;
match value {
OperationId::OperationIdV0(id) => self.serialize(id, buffer),
}
}
}
#[transition::impl_version(versions("0"), structures("OperationId"))]
impl Serializer<OperationId> for OperationIdSerializer {
fn serialize(&self, value: &OperationId, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
buffer.extend(value.0.to_bytes());
Ok(())
}
}
#[derive(Clone)]
pub struct OperationIdDeserializer {
hash_deserializer: HashDeserializer,
version_deserializer: U64VarIntDeserializer,
}
impl OperationIdDeserializer {
pub fn new() -> Self {
Self {
hash_deserializer: HashDeserializer::new(),
version_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
}
}
}
impl Default for OperationIdDeserializer {
fn default() -> Self {
Self::new()
}
}
impl Deserializer<OperationId> for OperationIdDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], OperationId, E> {
if buffer.len() < 2 {
return Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof)));
}
let (rest, op_id_version) =
self.version_deserializer
.deserialize(buffer)
.map_err(|_: nom::Err<E>| {
nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))
})?;
match op_id_version {
<OperationId!["0"]>::VERSION => {
let (rest, op_id) = self.deserialize(rest)?;
Ok((rest, OperationIdVariant!["0"](op_id)))
}
_ => Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))),
}
}
}
#[transition::impl_version(versions("0"), structures("OperationId"))]
impl Deserializer<OperationId> for OperationIdDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], OperationId, E> {
context("Failed OperationId deserialization", |input| {
self.hash_deserializer.deserialize(input)
})
.map(OperationId)
.parse(buffer)
}
}
#[derive(IntoPrimitive, Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u32)]
enum OperationTypeId {
Transaction = 0,
RollBuy = 1,
RollSell = 2,
ExecuteSC = 3,
CallSC = 4,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Operation {
pub fee: Amount,
pub expire_period: u64,
pub op: OperationType,
}
impl std::fmt::Display for Operation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Fee: {}", self.fee)?;
writeln!(f, "Expire period: {}", self.expire_period)?;
writeln!(f, "Operation type: {}", self.op)?;
Ok(())
}
}
pub type SecureShareOperation = SecureShare<Operation, OperationId>;
impl SecureShareContent for Operation {
fn compute_hash(
&self,
content_serialized: &[u8],
content_creator_pub_key: &PublicKey,
chain_id: u64,
) -> Hash {
let mut hash_data = Vec::new();
hash_data.extend(chain_id.to_be_bytes());
hash_data.extend(content_creator_pub_key.to_bytes());
hash_data.extend(content_serialized);
Hash::compute_from(&hash_data)
}
}
pub struct OperationSerializer {
u64_serializer: U64VarIntSerializer,
amount_serializer: AmountSerializer,
op_type_serializer: OperationTypeSerializer,
}
impl OperationSerializer {
pub fn new() -> Self {
Self {
u64_serializer: U64VarIntSerializer::new(),
amount_serializer: AmountSerializer::new(),
op_type_serializer: OperationTypeSerializer::new(),
}
}
}
impl Default for OperationSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<Operation> for OperationSerializer {
fn serialize(&self, value: &Operation, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
self.amount_serializer.serialize(&value.fee, buffer)?;
self.u64_serializer
.serialize(&value.expire_period, buffer)?;
self.op_type_serializer.serialize(&value.op, buffer)?;
Ok(())
}
}
pub struct OperationDeserializer {
expire_period_deserializer: U64VarIntDeserializer,
amount_deserializer: AmountDeserializer,
op_type_deserializer: OperationTypeDeserializer,
}
impl OperationDeserializer {
pub fn new(
max_datastore_value_length: u64,
max_function_name_length: u16,
max_parameters_size: u32,
max_op_datastore_entry_count: u64,
max_op_datastore_key_length: u8,
max_op_datastore_value_length: u64,
) -> Self {
Self {
expire_period_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
amount_deserializer: AmountDeserializer::new(
Included(Amount::MIN),
Included(Amount::MAX),
),
op_type_deserializer: OperationTypeDeserializer::new(
max_datastore_value_length,
max_function_name_length,
max_parameters_size,
max_op_datastore_entry_count,
max_op_datastore_key_length,
max_op_datastore_value_length,
),
}
}
}
impl Deserializer<Operation> for OperationDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Operation, E> {
context(
"Failed Operation deserialization",
tuple((
context("Failed fee deserialization", |input| {
self.amount_deserializer.deserialize(input)
}),
context("Failed expire_period deserialization", |input| {
self.expire_period_deserializer.deserialize(input)
}),
context("Failed op deserialization", |input| {
let (rest, op) = self.op_type_deserializer.deserialize(input)?;
Ok((rest, op))
}),
)),
)
.map(|(fee, expire_period, op)| Operation {
fee,
expire_period,
op,
})
.parse(buffer)
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum OperationType {
Transaction {
recipient_address: Address,
amount: Amount,
},
RollBuy {
roll_count: u64,
},
RollSell {
roll_count: u64,
},
ExecuteSC {
data: Vec<u8>,
max_gas: u64,
max_coins: Amount,
#[serde_as(as = "Vec<(_, _)>")]
datastore: Datastore,
},
CallSC {
target_addr: Address,
target_func: String,
param: Vec<u8>,
max_gas: u64,
coins: Amount,
},
}
impl std::fmt::Display for OperationType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OperationType::Transaction {
recipient_address,
amount,
} => {
writeln!(f, "Transaction:")?;
writeln!(f, "\t- Recipient:{}", recipient_address)?;
writeln!(f, "\t Amount:{}", amount)?;
}
OperationType::RollBuy { roll_count } => {
writeln!(f, "Buy rolls:")?;
writeln!(f, "\t- Roll count:{}", roll_count)?;
}
OperationType::RollSell { roll_count } => {
writeln!(f, "Sell rolls:")?;
writeln!(f, "\t- Roll count:{}", roll_count)?;
}
OperationType::ExecuteSC {
max_gas,
max_coins,
..
} => {
writeln!(f, "ExecuteSC: ")?;
writeln!(f, "\t- max_coins:{}", max_coins)?;
writeln!(f, "\t- max_gas:{}", max_gas)?;
},
OperationType::CallSC {
max_gas,
coins,
target_addr,
target_func,
param
} => {
writeln!(f, "CallSC:")?;
writeln!(f, "\t- target address:{}", target_addr)?;
writeln!(f, "\t- target function:{}", target_func)?;
writeln!(f, "\t- target parameter:{:?}", param)?;
writeln!(f, "\t- max_gas:{}", max_gas)?;
writeln!(f, "\t- coins:{}", coins)?;
}
}
Ok(())
}
}
pub struct OperationTypeSerializer {
u32_serializer: U32VarIntSerializer,
u64_serializer: U64VarIntSerializer,
vec_u8_serializer: VecU8Serializer,
amount_serializer: AmountSerializer,
address_serializer: AddressSerializer,
function_name_serializer: StringSerializer<U16VarIntSerializer, u16>,
datastore_serializer: DatastoreSerializer,
}
impl OperationTypeSerializer {
pub fn new() -> Self {
Self {
u32_serializer: U32VarIntSerializer::new(),
u64_serializer: U64VarIntSerializer::new(),
vec_u8_serializer: VecU8Serializer::new(),
amount_serializer: AmountSerializer::new(),
address_serializer: AddressSerializer::new(),
function_name_serializer: StringSerializer::new(U16VarIntSerializer::new()),
datastore_serializer: DatastoreSerializer::new(),
}
}
}
impl Default for OperationTypeSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<OperationType> for OperationTypeSerializer {
fn serialize(&self, value: &OperationType, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
match value {
OperationType::Transaction {
recipient_address,
amount,
} => {
self.u32_serializer
.serialize(&u32::from(OperationTypeId::Transaction), buffer)?;
self.address_serializer
.serialize(recipient_address, buffer)?;
self.amount_serializer.serialize(amount, buffer)?;
}
OperationType::RollBuy { roll_count } => {
self.u32_serializer
.serialize(&u32::from(OperationTypeId::RollBuy), buffer)?;
self.u64_serializer.serialize(roll_count, buffer)?;
}
OperationType::RollSell { roll_count } => {
self.u32_serializer
.serialize(&u32::from(OperationTypeId::RollSell), buffer)?;
self.u64_serializer.serialize(roll_count, buffer)?;
}
OperationType::ExecuteSC {
data,
max_gas,
max_coins,
datastore,
} => {
self.u32_serializer
.serialize(&u32::from(OperationTypeId::ExecuteSC), buffer)?;
self.u64_serializer.serialize(max_gas, buffer)?;
self.amount_serializer.serialize(max_coins, buffer)?;
self.vec_u8_serializer.serialize(data, buffer)?;
self.datastore_serializer.serialize(datastore, buffer)?;
}
OperationType::CallSC {
target_addr,
target_func,
param,
max_gas,
coins,
} => {
self.u32_serializer
.serialize(&u32::from(OperationTypeId::CallSC), buffer)?;
self.u64_serializer.serialize(max_gas, buffer)?;
self.amount_serializer.serialize(coins, buffer)?;
self.address_serializer.serialize(target_addr, buffer)?;
self.function_name_serializer
.serialize(target_func, buffer)?;
self.vec_u8_serializer.serialize(param, buffer)?;
}
}
Ok(())
}
}
pub struct OperationTypeDeserializer {
id_deserializer: U32VarIntDeserializer,
rolls_number_deserializer: U64VarIntDeserializer,
max_gas_deserializer: U64VarIntDeserializer,
address_deserializer: AddressDeserializer,
data_deserializer: VecU8Deserializer,
amount_deserializer: AmountDeserializer,
function_name_deserializer: StringDeserializer<U16VarIntDeserializer, u16>,
parameter_deserializer: VecU8Deserializer,
datastore_deserializer: DatastoreDeserializer,
}
impl OperationTypeDeserializer {
pub fn new(
max_datastore_value_length: u64,
max_function_name_length: u16,
max_parameters_size: u32,
max_op_datastore_entry_count: u64,
max_op_datastore_key_length: u8,
max_op_datastore_value_length: u64,
) -> Self {
Self {
id_deserializer: U32VarIntDeserializer::new(Included(0), Included(u32::MAX)),
rolls_number_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
max_gas_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
address_deserializer: AddressDeserializer::new(),
data_deserializer: VecU8Deserializer::new(
Included(0),
Included(max_datastore_value_length),
),
amount_deserializer: AmountDeserializer::new(
Included(Amount::MIN),
Included(Amount::MAX),
),
function_name_deserializer: StringDeserializer::new(U16VarIntDeserializer::new(
Included(0),
Included(max_function_name_length),
)),
parameter_deserializer: VecU8Deserializer::new(
Included(0),
Included(max_parameters_size as u64),
),
datastore_deserializer: DatastoreDeserializer::new(
max_op_datastore_entry_count,
max_op_datastore_key_length,
max_op_datastore_value_length,
),
}
}
}
impl Deserializer<OperationType> for OperationTypeDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], OperationType, E> {
context("Failed OperationType deserialization", |buffer| {
let (input, id) = self.id_deserializer.deserialize(buffer)?;
let id = OperationTypeId::try_from(id).map_err(|_| {
nom::Err::Error(ParseError::from_error_kind(
buffer,
nom::error::ErrorKind::Eof,
))
})?;
match id {
OperationTypeId::Transaction => context(
"Failed Transaction deserialization",
tuple((
context("Failed recipient_address deserialization", |input| {
self.address_deserializer.deserialize(input)
}),
context("Failed amount deserialization", |input| {
self.amount_deserializer.deserialize(input)
}),
)),
)
.map(|(recipient_address, amount)| OperationType::Transaction {
recipient_address,
amount,
})
.parse(input),
OperationTypeId::RollBuy => context("Failed RollBuy deserialization", |input| {
self.rolls_number_deserializer.deserialize(input)
})
.map(|roll_count| OperationType::RollBuy { roll_count })
.parse(input),
OperationTypeId::RollSell => context("Failed RollSell deserialization", |input| {
self.rolls_number_deserializer.deserialize(input)
})
.map(|roll_count| OperationType::RollSell { roll_count })
.parse(input),
OperationTypeId::ExecuteSC => context(
"Failed ExecuteSC deserialization",
tuple((
context("Failed max_gas deserialization", |input| {
self.max_gas_deserializer.deserialize(input)
}),
context("Failed max_coins deserialization", |input| {
self.amount_deserializer.deserialize(input)
}),
context("Failed data deserialization", |input| {
self.data_deserializer.deserialize(input)
}),
context("Failed datastore deserialization", |input| {
self.datastore_deserializer.deserialize(input)
}),
)),
)
.map(
|(max_gas, max_coins, data, datastore)| OperationType::ExecuteSC {
data,
max_gas,
max_coins,
datastore,
},
)
.parse(input),
OperationTypeId::CallSC => context(
"Failed CallSC deserialization",
tuple((
context("Failed max_gas deserialization", |input| {
self.max_gas_deserializer.deserialize(input)
}),
context("Failed coins deserialization", |input| {
self.amount_deserializer.deserialize(input)
}),
context("Failed target_addr deserialization", |input| {
self.address_deserializer.deserialize(input)
}),
context("Failed target_func deserialization", |input| {
self.function_name_deserializer.deserialize(input)
}),
context("Failed param deserialization", |input| {
self.parameter_deserializer.deserialize(input)
}),
)),
)
.map(
|(max_gas, coins, target_addr, target_func, param)| OperationType::CallSC {
target_addr,
target_func,
param,
max_gas,
coins,
},
)
.parse(input),
}
})
.parse(buffer)
}
}
impl SecureShareOperation {
pub fn get_validity_range(&self, operation_validity_period: u64) -> RangeInclusive<u64> {
let start = self
.content
.expire_period
.saturating_sub(operation_validity_period);
start..=self.content.expire_period
}
pub fn get_gas_usage(&self, base_operation_gas_cost: u64, sp_compilation_cost: u64) -> u64 {
match &self.content.op {
OperationType::ExecuteSC { max_gas, .. } => max_gas.saturating_add(sp_compilation_cost),
OperationType::CallSC { max_gas, .. } => *max_gas,
OperationType::RollBuy { .. } => 0,
OperationType::RollSell { .. } => 0,
OperationType::Transaction { .. } => 0,
}
.saturating_add(base_operation_gas_cost)
}
pub fn get_ledger_involved_addresses(&self) -> PreHashSet<Address> {
let mut res = PreHashSet::<Address>::default();
let emitter_address = Address::from_public_key(&self.content_creator_pub_key);
res.insert(emitter_address);
match &self.content.op {
OperationType::Transaction {
recipient_address, ..
} => {
res.insert(*recipient_address);
}
OperationType::RollBuy { .. } => {}
OperationType::RollSell { .. } => {}
OperationType::ExecuteSC { .. } => {}
OperationType::CallSC { target_addr, .. } => {
res.insert(*target_addr);
}
}
res
}
pub fn get_max_spending(&self, roll_price: Amount) -> Amount {
let max_non_fee_seq_spending = match &self.content.op {
OperationType::Transaction { amount, .. } => *amount,
OperationType::RollBuy { roll_count } => roll_price.saturating_mul_u64(*roll_count),
OperationType::RollSell { .. } => Amount::zero(),
OperationType::ExecuteSC { max_coins, .. } => *max_coins,
OperationType::CallSC { coins, .. } => *coins,
};
max_non_fee_seq_spending.saturating_add(self.content.fee)
}
pub fn get_roll_involved_addresses(&self) -> Result<PreHashSet<Address>, ModelsError> {
let mut res = PreHashSet::<Address>::default();
match self.content.op {
OperationType::Transaction { .. } => {}
OperationType::RollBuy { .. } => {
res.insert(Address::from_public_key(&self.content_creator_pub_key));
}
OperationType::RollSell { .. } => {
res.insert(Address::from_public_key(&self.content_creator_pub_key));
}
OperationType::ExecuteSC { .. } => {}
OperationType::CallSC { .. } => {}
}
Ok(res)
}
}
pub type OperationPrefixIds = PreHashSet<OperationPrefixId>;
pub struct OperationIdsSerializer {
u32_serializer: U32VarIntSerializer,
op_id_serializer: OperationIdSerializer,
}
impl OperationIdsSerializer {
pub fn new() -> Self {
Self {
u32_serializer: U32VarIntSerializer::new(),
op_id_serializer: OperationIdSerializer::new(),
}
}
}
impl Default for OperationIdsSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<Vec<OperationId>> for OperationIdsSerializer {
fn serialize(
&self,
value: &Vec<OperationId>,
buffer: &mut Vec<u8>,
) -> Result<(), SerializeError> {
let list_len: u32 = value.len().try_into().map_err(|_| {
SerializeError::NumberTooBig(
"could not encode Vec<OperationId> list length as u32".into(),
)
})?;
self.u32_serializer.serialize(&list_len, buffer)?;
for hash in value {
self.op_id_serializer.serialize(hash, buffer)?;
}
Ok(())
}
}
pub struct OperationIdsDeserializer {
length_deserializer: U32VarIntDeserializer,
op_id_deserializer: OperationIdDeserializer,
}
impl OperationIdsDeserializer {
pub fn new(max_operations_per_message: u32) -> Self {
Self {
length_deserializer: U32VarIntDeserializer::new(
Included(0),
Included(max_operations_per_message),
),
op_id_deserializer: OperationIdDeserializer::new(),
}
}
}
impl Deserializer<Vec<OperationId>> for OperationIdsDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Vec<OperationId>, E> {
context(
"Failed Vec<OperationId> deserialization",
length_count(
context("Failed length deserialization", |input| {
self.length_deserializer.deserialize(input)
}),
context("Failed OperationId deserialization", |input| {
self.op_id_deserializer.deserialize(input)
}),
),
)
.parse(buffer)
}
}
#[derive(Default)]
pub struct OperationPrefixIdDeserializer;
impl OperationPrefixIdDeserializer {
pub const fn new() -> Self {
Self
}
}
impl Deserializer<OperationPrefixId> for OperationPrefixIdDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], OperationPrefixId, E> {
context(
"Failed operation prefix id deserialization",
|input: &'a [u8]| {
if buffer.len() < OPERATION_ID_PREFIX_SIZE_BYTES {
return Err(nom::Err::Error(ParseError::from_error_kind(
input,
nom::error::ErrorKind::LengthValue,
)));
}
Ok((
&buffer[OPERATION_ID_PREFIX_SIZE_BYTES..],
OperationPrefixId::from(
&buffer[..OPERATION_ID_PREFIX_SIZE_BYTES]
.try_into()
.map_err(|_| {
nom::Err::Error(ParseError::from_error_kind(
input,
nom::error::ErrorKind::Fail,
))
})?,
),
))
},
)(buffer)
}
}
pub struct OperationPrefixIdsDeserializer {
length_deserializer: U32VarIntDeserializer,
pref_deserializer: OperationPrefixIdDeserializer,
}
impl OperationPrefixIdsDeserializer {
pub const fn new(max_operations_per_message: u32) -> Self {
Self {
length_deserializer: U32VarIntDeserializer::new(
Included(0),
Included(max_operations_per_message),
),
pref_deserializer: OperationPrefixIdDeserializer::new(),
}
}
}
impl Deserializer<OperationPrefixIds> for OperationPrefixIdsDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], OperationPrefixIds, E> {
context(
"Failed OperationPrefixIds deserialization",
length_count(
context("Failed length deserialization", |input| {
self.length_deserializer.deserialize(input)
}),
context("Failed OperationPrefixId deserialization", |input| {
self.pref_deserializer.deserialize(input)
}),
),
)
.map(|hashes| hashes.into_iter().collect())
.parse(buffer)
}
}
#[derive(Clone)]
pub struct OperationPrefixIdsSerializer {
u32_serializer: U32VarIntSerializer,
}
impl OperationPrefixIdsSerializer {
pub const fn new() -> Self {
Self {
u32_serializer: U32VarIntSerializer::new(),
}
}
}
impl Default for OperationPrefixIdsSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<OperationPrefixIds> for OperationPrefixIdsSerializer {
fn serialize(
&self,
value: &OperationPrefixIds,
buffer: &mut Vec<u8>,
) -> Result<(), SerializeError> {
let list_len: u32 = value.len().try_into().map_err(|_| {
SerializeError::NumberTooBig(
"could not encode Set<OperationId> list length as u32".into(),
)
})?;
self.u32_serializer.serialize(&list_len, buffer)?;
for prefix in value {
buffer.extend(Vec::<u8>::from(prefix));
}
Ok(())
}
}
#[derive(Clone)]
pub struct OperationsSerializer {
u32_serializer: U32VarIntSerializer,
signed_op_serializer: SecureShareSerializer,
}
impl OperationsSerializer {
pub const fn new() -> Self {
Self {
u32_serializer: U32VarIntSerializer::new(),
signed_op_serializer: SecureShareSerializer::new(),
}
}
}
impl Default for OperationsSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<Vec<SecureShareOperation>> for OperationsSerializer {
fn serialize(
&self,
value: &Vec<SecureShareOperation>,
buffer: &mut Vec<u8>,
) -> Result<(), SerializeError> {
let list_len: u32 = value.len().try_into().map_err(|_| {
SerializeError::NumberTooBig("could not encode Operations list length as u32".into())
})?;
self.u32_serializer.serialize(&list_len, buffer)?;
for op in value {
self.signed_op_serializer.serialize(op, buffer)?;
}
Ok(())
}
}
pub struct OperationsDeserializer {
length_deserializer: U32VarIntDeserializer,
signed_op_deserializer: SecureShareDeserializer<Operation, OperationDeserializer>,
}
impl OperationsDeserializer {
#[allow(clippy::too_many_arguments)]
pub fn new(
max_operations_per_message: u32,
max_datastore_value_length: u64,
max_function_name_length: u16,
max_parameters_size: u32,
max_op_datastore_entry_count: u64,
max_op_datastore_key_length: u8,
max_op_datastore_value_length: u64,
chain_id: u64,
) -> Self {
Self {
length_deserializer: U32VarIntDeserializer::new(
Included(0),
Included(max_operations_per_message),
),
signed_op_deserializer: SecureShareDeserializer::new(
OperationDeserializer::new(
max_datastore_value_length,
max_function_name_length,
max_parameters_size,
max_op_datastore_entry_count,
max_op_datastore_key_length,
max_op_datastore_value_length,
),
chain_id,
),
}
}
}
impl Deserializer<Vec<SecureShareOperation>> for OperationsDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Vec<SecureShareOperation>, E> {
context(
"Failed Operations deserialization",
length_count(
context("Failed length deserialization", |input| {
self.length_deserializer.deserialize(input)
}),
context("Failed operation deserialization", |input| {
self.signed_op_deserializer.deserialize(input)
}),
),
)
.parse(buffer)
}
}
pub fn compute_operations_hash(
op_ids: &[OperationId],
op_id_serializer: &OperationIdSerializer,
) -> Hash {
let op_ids = op_ids
.iter()
.map(|op_id| {
let mut serialized = Vec::new();
op_id_serializer
.serialize(op_id, &mut serialized)
.expect("serialization of operation id should not fail");
serialized
})
.collect::<Vec<Vec<u8>>>();
massa_hash::Hash::compute_from_tuple(
&op_ids
.iter()
.map(|data| data.as_slice())
.collect::<Vec<_>>(),
)
}
#[cfg(test)]
mod tests {
use crate::config::{
CHAINID, MAX_DATASTORE_VALUE_LENGTH, MAX_FUNCTION_NAME_LENGTH,
MAX_OPERATION_DATASTORE_ENTRY_COUNT, MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH, MAX_PARAMETERS_SIZE,
};
use super::*;
use massa_serialization::DeserializeError;
use massa_signature::KeyPair;
use serde_json::Value;
use serial_test::serial;
use std::collections::BTreeMap;
#[test]
#[serial]
fn test_transaction_massa_docs() {
let chain_id: u64 = 77658383;
let sk = "S1CkpvD4WMjJWxR2WZcrDEkJ1kWG2kKe1e3Afe8miqmskHqovvA";
let pk = "P1t4JZwHhWNLt4xYabCbukyVNxSbhYPdF6wCYuRmDuHD784juxd";
let _addr = "AU12m1gXHUGxBZsDF4veeWfYaRmpztBCieHhPBaqf3fcRF2LdAuZ7";
let op_fee = "0.001";
let op_expiry_period = 1000;
let tx_dest_addr = "AU12v83xmHg2UrLM8GLsXRMrm7LQgn3DZVT6kUeFsuFyhZKLkbQtY";
let tx_amount = "3.1";
let op_type = OperationType::Transaction {
recipient_address: Address::from_str(tx_dest_addr).unwrap(),
amount: Amount::from_str(tx_amount).unwrap(),
};
let content = Operation {
fee: Amount::from_str(op_fee).unwrap(),
op: op_type,
expire_period: op_expiry_period,
};
let op_serializer = OperationSerializer::new();
let sender_keypair = KeyPair::from_str(sk).unwrap();
assert_eq!(
sender_keypair.get_public_key(),
PublicKey::from_str(pk).unwrap()
);
let op: SecureShare<Operation, OperationId> =
Operation::new_verifiable(content, op_serializer, &sender_keypair, chain_id).unwrap();
println!("Chain id: {}", *CHAINID);
println!("Operation id: {:?}", op.id);
let mut ser_op = Vec::new();
SecureShareSerializer::new()
.serialize(&op, &mut ser_op)
.unwrap();
println!("Operation serialized:");
println!("{:02X?}", ser_op);
}
#[test]
#[serial]
fn test_transaction() {
let sender_keypair = KeyPair::generate(0).unwrap();
let recv_keypair = KeyPair::generate(0).unwrap();
let op = OperationType::Transaction {
recipient_address: Address::from_public_key(&recv_keypair.get_public_key()),
amount: Amount::default(),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let content = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let mut ser_content = Vec::new();
OperationSerializer::new()
.serialize(&content, &mut ser_content)
.unwrap();
let (_, res_content) = OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_content)
.unwrap();
assert_eq!(res_content, content);
let op_serializer = OperationSerializer::new();
let op =
Operation::new_verifiable(content, op_serializer, &sender_keypair, *CHAINID).unwrap();
let mut ser_op = Vec::new();
SecureShareSerializer::new()
.serialize(&op, &mut ser_op)
.unwrap();
let (_, res_op): (&[u8], SecureShareOperation) = SecureShareDeserializer::new(
OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
),
*CHAINID,
)
.deserialize::<DeserializeError>(&ser_op)
.unwrap();
assert_eq!(res_op, op);
assert_eq!(op.get_validity_range(10), 40..=50);
}
#[test]
#[serial]
fn test_executesc() {
let sender_keypair = KeyPair::generate(0).unwrap();
let op = OperationType::ExecuteSC {
max_gas: 123,
max_coins: Amount::from_str("1.0").unwrap(),
data: vec![23u8, 123u8, 44u8],
datastore: BTreeMap::from([
(vec![1, 2, 3], vec![4, 5, 6, 7, 8, 9]),
(vec![22, 33, 44, 55, 66, 77], vec![11]),
]),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let content = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let mut ser_content = Vec::new();
OperationSerializer::new()
.serialize(&content, &mut ser_content)
.unwrap();
let (_, res_content) = OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_content)
.unwrap();
assert_eq!(res_content, content);
let op_serializer = OperationSerializer::new();
let op =
Operation::new_verifiable(content, op_serializer, &sender_keypair, *CHAINID).unwrap();
let mut ser_op = Vec::new();
SecureShareSerializer::new()
.serialize(&op, &mut ser_op)
.unwrap();
let (_, res_op): (&[u8], SecureShareOperation) = SecureShareDeserializer::new(
OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
),
*CHAINID,
)
.deserialize::<DeserializeError>(&ser_op)
.unwrap();
assert_eq!(res_op, op);
assert_eq!(op.get_validity_range(10), 40..=50);
}
#[test]
#[serial]
fn test_callsc() {
let sender_keypair = KeyPair::generate(0).unwrap();
let target_keypair = KeyPair::generate(0).unwrap();
let target_addr = Address::from_public_key(&target_keypair.get_public_key());
let op = OperationType::CallSC {
max_gas: 123,
target_addr,
coins: Amount::from_str("456.789").unwrap(),
target_func: "target function".to_string(),
param: b"parameter".to_vec(),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let content = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let mut ser_content = Vec::new();
OperationSerializer::new()
.serialize(&content, &mut ser_content)
.unwrap();
let (_, res_content) = OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_content)
.unwrap();
assert_eq!(res_content, content);
let op_serializer = OperationSerializer::new();
let op =
Operation::new_verifiable(content, op_serializer, &sender_keypair, *CHAINID).unwrap();
let mut ser_op = Vec::new();
SecureShareSerializer::new()
.serialize(&op, &mut ser_op)
.unwrap();
let (_, res_op): (&[u8], SecureShareOperation) = SecureShareDeserializer::new(
OperationDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
),
*CHAINID,
)
.deserialize::<DeserializeError>(&ser_op)
.unwrap();
assert_eq!(res_op, op);
assert_eq!(op.get_validity_range(10), 40..=50);
}
#[test]
#[serial]
fn test_transaction_serde() {
let recv_keypair = KeyPair::generate(0).unwrap();
let op: OperationType = OperationType::Transaction {
recipient_address: Address::from_public_key(&recv_keypair.get_public_key()),
amount: Amount::default(),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let orig_operation = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let serialized_operation = serde_json::to_string(&orig_operation).unwrap();
let res_operation: Value = serde_json::from_str(&serialized_operation).unwrap();
assert_eq!(orig_operation.fee.to_string(), res_operation["fee"]);
assert_eq!(orig_operation.expire_period, res_operation["expire_period"]);
}
#[test]
#[serial]
fn test_executesc_serde() {
let op = OperationType::ExecuteSC {
max_gas: 123,
max_coins: Amount::from_str("1.0").unwrap(),
data: vec![23u8, 123u8, 44u8],
datastore: BTreeMap::from([
(vec![1, 2, 3], vec![4, 5, 6, 7, 8, 9]),
(vec![22, 33, 44, 55, 66, 77], vec![11]),
]),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let orig_operation = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let serialized_operation = serde_json::to_string(&orig_operation).unwrap();
let res_operation: Value = serde_json::from_str(&serialized_operation).unwrap();
assert_eq!(orig_operation.fee.to_string(), res_operation["fee"]);
assert_eq!(orig_operation.expire_period, res_operation["expire_period"]);
}
#[test]
#[serial]
fn test_callsc_serde() {
let target_keypair = KeyPair::generate(0).unwrap();
let target_addr = Address::from_public_key(&target_keypair.get_public_key());
let op = OperationType::CallSC {
max_gas: 123,
target_addr,
coins: Amount::from_str("456.789").unwrap(),
target_func: "target function".to_string(),
param: b"parameter".to_vec(),
};
let mut ser_type = Vec::new();
OperationTypeSerializer::new()
.serialize(&op, &mut ser_type)
.unwrap();
let (_, res_type) = OperationTypeDeserializer::new(
MAX_DATASTORE_VALUE_LENGTH,
MAX_FUNCTION_NAME_LENGTH,
MAX_PARAMETERS_SIZE,
MAX_OPERATION_DATASTORE_ENTRY_COUNT,
MAX_OPERATION_DATASTORE_KEY_LENGTH,
MAX_OPERATION_DATASTORE_VALUE_LENGTH,
)
.deserialize::<DeserializeError>(&ser_type)
.unwrap();
assert_eq!(res_type, op);
let orig_operation = Operation {
fee: Amount::from_str("20").unwrap(),
op,
expire_period: 50,
};
let serialized_operation = serde_json::to_string(&orig_operation).unwrap();
let res_operation: Value = serde_json::from_str(&serialized_operation).unwrap();
assert_eq!(orig_operation.fee.to_string(), res_operation["fee"]);
assert_eq!(orig_operation.expire_period, res_operation["expire_period"]);
}
}