Skip to content

Commit

Permalink
Refactored readable serde and fix SuiAddress serialization (MystenLab…
Browse files Browse the repository at this point in the history
…s#1487)

* * Refactored readable serde to use serde_as instead, greatly simplified the code and removed a lot of duplicated logics
* Changed SuiAddress serde to tuple array instead of Bytes
* Refactored ObjectID serde to reuse readable_serde, without changing the output
* Fixed/Improved serde for MoveObject and MovePackage

* add docs

* update format

* remove code block quote from Readable docs

* add serialization test for SuiAddress and ObjectID with hardcoded values
patrickkuo authored Apr 20, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 4fa31b4 commit de86f44
Showing 11 changed files with 203 additions and 234 deletions.
1 change: 0 additions & 1 deletion sui_core/src/authority/authority_store.rs
Original file line number Diff line number Diff line change
@@ -1057,7 +1057,6 @@ impl<const A: bool, S: Eq + Serialize + for<'de> Deserialize<'de>> ModuleResolve
.serialized_module_map()
.get(module_id.name().as_str())
.cloned()
.map(|m| m.into_vec())
}))
}
}
3 changes: 1 addition & 2 deletions sui_core/src/authority/temporary_store.rs
Original file line number Diff line number Diff line change
@@ -358,8 +358,7 @@ impl<S: BackingPackageStore> ModuleResolver for AuthorityTemporaryStore<S> {
Data::Package(c) => Ok(c
.serialized_module_map()
.get(module_id.name().as_str())
.cloned()
.map(|m| m.into_vec())),
.cloned()),
_ => Err(SuiError::BadObjectType {
error: "Expected module object".to_string(),
}),
10 changes: 6 additions & 4 deletions sui_core/tests/staged/sui.yaml
Original file line number Diff line number Diff line change
@@ -208,13 +208,12 @@ MoveFieldLayout:
MoveModulePublish:
STRUCT:
- modules:
SEQ:
SEQ: U8
SEQ: BYTES
MoveObject:
STRUCT:
- type_:
TYPENAME: StructTag
- contents: STR
- contents: BYTES
MovePackage:
STRUCT:
- id:
@@ -435,7 +434,10 @@ StructTag:
SEQ:
TYPENAME: TypeTag
SuiAddress:
NEWTYPESTRUCT: BYTES
NEWTYPESTRUCT:
TUPLEARRAY:
CONTENT: U8
SIZE: 20
SuiError:
ENUM:
0:
8 changes: 3 additions & 5 deletions sui_programmability/adapter/src/unit_tests/adapter_tests.rs
Original file line number Diff line number Diff line change
@@ -160,11 +160,9 @@ impl ModuleResolver for InMemoryStorage {
Ok(self
.read_object(&ObjectID::from(*module_id.address()))
.and_then(|o| match &o.data {
Data::Package(m) => Some(
m.serialized_module_map()[module_id.name().as_str()]
.clone()
.into_vec(),
),
Data::Package(m) => {
Some(m.serialized_module_map()[module_id.name().as_str()].clone())
}
Data::Move(_) => None,
}))
}
70 changes: 20 additions & 50 deletions sui_types/src/base_types.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// Copyright (c) 2021, Facebook, Inc. and its affiliates
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use base64ct::{Base64, Encoding};
use base64ct::Encoding;
use std::collections::{HashMap, HashSet};
use std::convert::{TryFrom, TryInto};
use std::fmt;

use crate::readable_serde::BytesOrBase64;
use crate::readable_serde::BytesOrHex;
use crate::crypto::PublicKeyBytes;
use crate::error::SuiError;
use crate::readable_serde::encoding::Base64;
use crate::readable_serde::encoding::Hex;
use crate::readable_serde::Readable;
use ed25519_dalek::Digest;
use hex::FromHex;
use move_core_types::account_address::AccountAddress;
use move_core_types::ident_str;
use move_core_types::identifier::IdentStr;
use opentelemetry::{global, Context};
use rand::Rng;
use serde::de::Error;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use serde_with::Bytes;
use sha3::Sha3_256;

use crate::crypto::PublicKeyBytes;
use crate::error::SuiError;

#[cfg(test)]
#[path = "unit_tests/base_types_tests.rs"]
mod base_types_tests;
@@ -38,14 +38,16 @@ pub struct UserData(pub Option<[u8; 32]>);

pub type AuthorityName = PublicKeyBytes;

#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash)]
pub struct ObjectID(AccountAddress);
#[serde_as]
#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ObjectID(#[serde_as(as = "Readable<Hex, _>")] AccountAddress);

pub type ObjectRef = (ObjectID, SequenceNumber, ObjectDigest);

pub const SUI_ADDRESS_LENGTH: usize = ObjectID::LENGTH;
#[serde_as]
#[derive(Eq, Default, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
pub struct SuiAddress(#[serde(with = "BytesOrHex")] [u8; SUI_ADDRESS_LENGTH]);
pub struct SuiAddress(#[serde_as(as = "Readable<Hex, _>")] [u8; SUI_ADDRESS_LENGTH]);

impl SuiAddress {
pub fn to_vec(&self) -> Vec<u8> {
@@ -133,11 +135,15 @@ pub const TRANSACTION_DIGEST_LENGTH: usize = 32;
pub const OBJECT_DIGEST_LENGTH: usize = 32;

/// A transaction will have a (unique) digest.
#[serde_as]
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
pub struct TransactionDigest(#[serde(with = "BytesOrBase64")] [u8; TRANSACTION_DIGEST_LENGTH]);
pub struct TransactionDigest(
#[serde_as(as = "Readable<Base64, Bytes>")] [u8; TRANSACTION_DIGEST_LENGTH],
);
// Each object has a unique digest
#[serde_as]
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
pub struct ObjectDigest(#[serde(with = "BytesOrBase64")] pub [u8; 32]); // We use SHA3-256 hence 32 bytes here
pub struct ObjectDigest(#[serde_as(as = "Readable<Base64, Bytes>")] pub [u8; 32]); // We use SHA3-256 hence 32 bytes here

pub const TX_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("TxContext");
pub const TX_CONTEXT_STRUCT_NAME: &IdentStr = TX_CONTEXT_MODULE_NAME;
@@ -393,7 +399,7 @@ impl TryFrom<&[u8]> for ObjectDigest {

impl std::fmt::Debug for TransactionDigest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let s = Base64::encode_string(&self.0);
let s = base64ct::Base64::encode_string(&self.0);
write!(f, "{}", s)?;
Ok(())
}
@@ -639,39 +645,3 @@ impl std::str::FromStr for ObjectID {
Self::from_hex(s).or_else(|_| Self::from_hex_literal(s))
}
}

impl<'de> Deserialize<'de> for ObjectID {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <String>::deserialize(deserializer)?;
ObjectID::from_hex(s).map_err(D::Error::custom)
} else {
// In order to preserve the Serde data model and help analysis tools,
// make sure to wrap our value in a container with the same name
// as the original type.
#[derive(::serde::Deserialize)]
#[serde(rename = "ObjectID")]
struct Value([u8; ObjectID::LENGTH]);

let value = Value::deserialize(deserializer)?;
Ok(ObjectID::new(value.0))
}
}
}

impl Serialize for ObjectID {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
self.to_hex().serialize(serializer)
} else {
// See comment in deserialize.
serializer.serialize_newtype_struct("ObjectID", &self.0)
}
}
}
28 changes: 18 additions & 10 deletions sui_types/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use anyhow::Error;
use base64ct::{Base64, Encoding};
use base64ct::Encoding;
use std::borrow::Borrow;
use std::collections::HashMap;

use crate::base_types::{AuthorityName, SuiAddress};
use crate::error::{SuiError, SuiResult};
use crate::readable_serde::BytesOrBase64;
use crate::readable_serde::encoding::Base64;
use crate::readable_serde::Readable;
use ed25519_dalek as dalek;
use ed25519_dalek::{Digest, PublicKey, Verifier};
use once_cell::sync::OnceCell;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use serde_with::Bytes;
use sha3::Sha3_256;

// TODO: Make sure secrets are not copyable and movable to control where they are in memory
@@ -49,7 +52,7 @@ impl Serialize for KeyPair {
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&Base64::encode_string(&self.key_pair.to_bytes()))
serializer.serialize_str(&base64ct::Base64::encode_string(&self.key_pair.to_bytes()))
}
}

@@ -59,8 +62,8 @@ impl<'de> Deserialize<'de> for KeyPair {
D: serde::de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let value =
Base64::decode_vec(&s).map_err(|err| serde::de::Error::custom(err.to_string()))?;
let value = base64ct::Base64::decode_vec(&s)
.map_err(|err| serde::de::Error::custom(err.to_string()))?;
let key = dalek::Keypair::from_bytes(&value)
.map_err(|err| serde::de::Error::custom(err.to_string()))?;
Ok(KeyPair {
@@ -88,8 +91,11 @@ impl signature::Signer<AuthoritySignature> for KeyPair {
}
}

#[serde_as]
#[derive(Eq, Default, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize)]
pub struct PublicKeyBytes(#[serde(with = "BytesOrBase64")] [u8; dalek::PUBLIC_KEY_LENGTH]);
pub struct PublicKeyBytes(
#[serde_as(as = "Readable<Base64, Bytes>")] [u8; dalek::PUBLIC_KEY_LENGTH],
);

impl PublicKeyBytes {
pub fn to_vec(&self) -> Vec<u8> {
@@ -165,8 +171,9 @@ pub fn get_key_pair_from_bytes(bytes: &[u8]) -> (SuiAddress, KeyPair) {
pub const SUI_SIGNATURE_LENGTH: usize =
ed25519_dalek::PUBLIC_KEY_LENGTH + ed25519_dalek::SIGNATURE_LENGTH;

#[serde_as]
#[derive(Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub struct Signature(#[serde(with = "BytesOrBase64")] [u8; SUI_SIGNATURE_LENGTH]);
pub struct Signature(#[serde_as(as = "Readable<Base64, Bytes>")] [u8; SUI_SIGNATURE_LENGTH]);

impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
@@ -184,8 +191,8 @@ impl signature::Signature for Signature {

impl std::fmt::Debug for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let s = Base64::encode_string(self.signature_bytes());
let p = Base64::encode_string(self.public_key_bytes());
let s = base64ct::Base64::encode_string(self.signature_bytes());
let p = base64ct::Base64::encode_string(self.public_key_bytes());
write!(f, "{s}@{p}")?;
Ok(())
}
@@ -297,8 +304,9 @@ impl Signature {

/// A signature emitted by an authority. It's useful to decouple this from user signatures,
/// as their set of supported schemes will probably diverge
#[serde_as]
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub struct AuthoritySignature(#[serde(with = "BytesOrBase64")] pub dalek::Signature);
pub struct AuthoritySignature(#[serde_as(as = "Readable<Base64, _>")] pub dalek::Signature);
impl AsRef<[u8]> for AuthoritySignature {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
14 changes: 7 additions & 7 deletions sui_types/src/messages.rs
Original file line number Diff line number Diff line change
@@ -2,14 +2,16 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::{base_types::*, batch::*, committee::Committee, error::*, event::Event};
use crate::crypto::{
sha3_hash, AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature, BcsSignable,
EmptySignInfo, Signable, Signature, VerificationObligation,
};
use crate::gas::GasCostSummary;
use crate::object::{Object, ObjectFormatOptions, Owner, OBJECT_START_VERSION};
use crate::readable_serde::Base64OrDefault;
use base64ct::{Base64, Encoding};
use crate::readable_serde::encoding::Base64;
use crate::readable_serde::Readable;
use base64ct::Encoding;
use itertools::Either;
use move_binary_format::{access::ModuleAccess, CompiledModule};
use move_core_types::{
@@ -21,15 +23,13 @@ use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use serde_name::{DeserializeNameAdapter, SerializeNameAdapter};
use serde_with::serde_as;
use serde_with::Bytes;
use std::fmt::Write;
use std::fmt::{Display, Formatter};
use std::{
collections::{BTreeSet, HashSet},
hash::{Hash, Hasher},
};

use super::{base_types::*, batch::*, committee::Committee, error::*, event::Event};

#[cfg(test)]
#[path = "unit_tests/messages_tests.rs"]
mod messages_tests;
@@ -59,7 +59,7 @@ pub struct MoveCall {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct MoveModulePublish {
#[serde_as(as = "Vec<Base64OrDefault>")]
#[serde_as(as = "Vec<Readable<Base64, Bytes>>")]
pub modules: Vec<Vec<u8>>,
}

@@ -319,7 +319,7 @@ where
}

pub fn to_base64(&self) -> String {
Base64::encode_string(&self.to_bytes())
base64ct::Base64::encode_string(&self.to_bytes())
}
}

16 changes: 9 additions & 7 deletions sui_types/src/move_package.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::readable_serde::Base64OrDefault;
use crate::readable_serde::encoding::Base64;
use crate::readable_serde::Readable;
use crate::{
base_types::ObjectID,
error::{SuiError, SuiResult},
};
use move_binary_format::file_format::CompiledModule;
use move_core_types::identifier::Identifier;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use serde_with::serde_as;
use serde_with::Bytes;
use std::collections::BTreeMap;

// TODO: robust MovePackage tests
// #[cfg(test)]
// #[path = "unit_tests/move_package.rs"]
@@ -22,13 +24,13 @@ use std::collections::BTreeMap;
#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
pub struct MovePackage {
id: ObjectID,
#[serde_as(as = "BTreeMap<_, Base64OrDefault>")]
// TODO use session cache
module_map: BTreeMap<String, ByteBuf>,
#[serde_as(as = "BTreeMap<_, Readable<Base64, Bytes>>")]
module_map: BTreeMap<String, Vec<u8>>,
}

impl MovePackage {
pub fn new(id: ObjectID, module_map: &BTreeMap<String, ByteBuf>) -> Self {
pub fn new(id: ObjectID, module_map: &BTreeMap<String, Vec<u8>>) -> Self {
Self {
id,
module_map: module_map.clone(),
@@ -39,7 +41,7 @@ impl MovePackage {
self.id
}

pub fn serialized_module_map(&self) -> &BTreeMap<String, ByteBuf> {
pub fn serialized_module_map(&self) -> &BTreeMap<String, Vec<u8>> {
&self.module_map
}

@@ -67,7 +69,7 @@ impl From<&Vec<CompiledModule>> for MovePackage {
.map(|module| {
let mut bytes = Vec::new();
module.serialize(&mut bytes).unwrap();
(module.self_id().name().to_string(), ByteBuf::from(bytes))
(module.self_id().name().to_string(), bytes)
})
.collect(),
)
6 changes: 4 additions & 2 deletions sui_types/src/object.rs
Original file line number Diff line number Diff line change
@@ -16,13 +16,15 @@ use move_disassembler::disassembler::Disassembler;
use move_ir_types::location::Spanned;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use serde_with::base64::Base64;
use serde_with::serde_as;
use serde_with::Bytes;

use crate::coin::Coin;
use crate::crypto::{sha3_hash, BcsSignable};
use crate::error::{SuiError, SuiResult};
use crate::move_package::MovePackage;
use crate::readable_serde::encoding::Base64;
use crate::readable_serde::Readable;
use crate::{
base_types::{
ObjectDigest, ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest,
@@ -37,7 +39,7 @@ pub const OBJECT_START_VERSION: SequenceNumber = SequenceNumber::from_u64(1);
#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
pub struct MoveObject {
pub type_: StructTag,
#[serde_as(as = "Base64")]
#[serde_as(as = "Readable<Base64, Bytes>")]
contents: Vec<u8>,
}

247 changes: 104 additions & 143 deletions sui_types/src/readable_serde.rs
Original file line number Diff line number Diff line change
@@ -1,206 +1,167 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use anyhow::anyhow;
use std::fmt::Display;
use move_core_types::account_address::AccountAddress;
use std::fmt::Debug;
use std::marker::PhantomData;

use base64ct::{Base64, Encoding as _};
use serde;
use serde::de::{Deserialize, Deserializer, Error};
use serde::ser::Serializer;
use serde::Serialize;
use serde_bytes::ByteBuf;
use serde_with::{Bytes, DeserializeAs, SerializeAs};
use serde_with::{DeserializeAs, SerializeAs};

/// Encode bytes to hex for human-readable serializer and deserializer,
/// serde to bytes for non-human-readable serializer and deserializer.
pub trait BytesOrHex<'de>: Sized {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Self: AsRef<[u8]>,
{
serialize_to_bytes_or_encode(serializer, self.as_ref(), Encoding::Hex)
}
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}

/// Encode bytes to Base64 for human-readable serializer and deserializer,
/// serde to array tuple for non-human-readable serializer and deserializer.
pub trait BytesOrBase64<'de>: Sized {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Self: AsRef<[u8]>,
{
serialize_to_bytes_or_encode(serializer, self.as_ref(), Encoding::Base64)
}
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
use crate::readable_serde::encoding::Encoding;

fn deserialize_from_bytes_or_decode<'de, D>(
deserializer: D,
encoding: Encoding,
) -> Result<Vec<u8>, D::Error>
fn to_custom_error<'de, D, E>(e: E) -> D::Error
where
E: Debug,
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
encoding.decode(s).map_err(to_custom_error::<'de, D, _>)
} else {
Bytes::deserialize_as(deserializer)
}
D::Error::custom(format!("byte deserialization failed, cause by: {:?}", e))
}

fn serialize_to_bytes_or_encode<S>(
serializer: S,
data: &[u8],
encoding: Encoding,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
encoding.encode(data).serialize(serializer)
} else {
Bytes::serialize_as(&data, serializer)
}
/// Use with serde_as to encode/decode bytes to/from Base64/Hex for human-readable serializer and deserializer
/// E : Encoding of the human readable output
/// R : serde_as SerializeAs/DeserializeAs delegation
///
/// # Example:
///
/// #[serde_as]
/// #[derive(Deserialize, Serialize)]
/// struct Example(#[serde_as(as = "Readable(Hex, _)")] [u8; 20]);
///
/// The above example will encode the byte array to Hex string for human-readable serializer
/// and array tuple (default) for non-human-readable serializer.
///
pub struct Readable<E, R> {
element: PhantomData<R>,
encoding: PhantomData<E>,
}

impl<'de> BytesOrBase64<'de> for ed25519_dalek::Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
impl<T, R, E> SerializeAs<T> for Readable<E, R>
where
T: AsRef<[u8]>,
R: SerializeAs<T>,
E: Encoding,
{
fn serialize_as<S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Self: AsRef<[u8]>,
{
if serializer.is_human_readable() {
Encoding::Base64.encode(self.as_ref()).serialize(serializer)
E::encode(value).serialize(serializer)
} else {
<Self as Serialize>::serialize(self, serializer)
R::serialize_as(value, serializer)
}
}
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
}
/// DeserializeAs support for Arrays
impl<'de, R, E, const N: usize> DeserializeAs<'de, [u8; N]> for Readable<E, R>
where
R: DeserializeAs<'de, [u8; N]>,
E: Encoding,
{
fn deserialize_as<D>(deserializer: D) -> Result<[u8; N], D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
let value = Encoding::Base64
.decode(s)
.map_err(to_custom_error::<'de, D, _>)?;
Self::try_from(value.as_slice()).map_err(to_custom_error::<'de, D, _>)
let value = E::decode(s).map_err(to_custom_error::<'de, D, _>)?;
let mut array = [0u8; N];
array.copy_from_slice(&value[..N]);
Ok(array)
} else {
<Self as Deserialize>::deserialize(deserializer)
R::deserialize_as(deserializer)
}
}
}

fn to_custom_error<'de, D, E>(e: E) -> D::Error
/// DeserializeAs support for Vec
impl<'de, R, E> DeserializeAs<'de, Vec<u8>> for Readable<E, R>
where
E: Display,
D: Deserializer<'de>,
R: DeserializeAs<'de, Vec<u8>>,
E: Encoding,
{
D::Error::custom(format!("byte deserialization failed, cause by: {}", e))
}

impl<'de, const N: usize> BytesOrHex<'de> for [u8; N] {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_from_bytes_or_decode(deserializer, Encoding::Hex)?;
let mut array = [0u8; N];
array.copy_from_slice(&value[..N]);
Ok(array)
}
}

impl<'de, const N: usize> BytesOrBase64<'de> for [u8; N] {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_from_bytes_or_decode(deserializer, Encoding::Base64)?;
let mut array = [0u8; N];
array.copy_from_slice(&value[..N]);
Ok(array)
}
}

enum Encoding {
Base64,
Hex,
}

impl Encoding {
fn decode(&self, s: String) -> Result<Vec<u8>, anyhow::Error> {
Ok(match self {
Encoding::Base64 => Base64::decode_vec(&s).map_err(|e| anyhow!(e))?,
Encoding::Hex => hex::decode(s)?,
})
}

fn encode<T: AsRef<[u8]>>(&self, data: T) -> String {
match self {
Encoding::Base64 => Base64::encode_string(data.as_ref()),
Encoding::Hex => hex::encode(data),
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
E::decode(s).map_err(to_custom_error::<'de, D, _>)
} else {
R::deserialize_as(deserializer)
}
}
}

pub struct Base64OrDefault;

impl<T> SerializeAs<T> for Base64OrDefault
/// DeserializeAs support for Signature
impl<'de, R, E> DeserializeAs<'de, ed25519_dalek::Signature> for Readable<E, R>
where
T: AsRef<[u8]> + Serialize,
R: DeserializeAs<'de, ed25519_dalek::Signature>,
E: Encoding,
{
fn serialize_as<S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
fn deserialize_as<D>(deserializer: D) -> Result<ed25519_dalek::Signature, D::Error>
where
S: Serializer,
D: Deserializer<'de>,
{
if serializer.is_human_readable() {
Encoding::Base64.encode(value).serialize(serializer)
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
let value = E::decode(s).map_err(to_custom_error::<'de, D, _>)?;
ed25519_dalek::Signature::from_bytes(&value).map_err(to_custom_error::<'de, D, _>)
} else {
<T as Serialize>::serialize(value, serializer)
R::deserialize_as(deserializer)
}
}
}

impl<'de> DeserializeAs<'de, Vec<u8>> for Base64OrDefault {
fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
/// DeserializeAs support for AccountAddress
impl<'de, R, E> DeserializeAs<'de, AccountAddress> for Readable<E, R>
where
R: DeserializeAs<'de, AccountAddress>,
E: Encoding,
{
fn deserialize_as<D>(deserializer: D) -> Result<AccountAddress, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
Encoding::Base64
.decode(s)
.map_err(to_custom_error::<'de, D, _>)
let value = E::decode(s).map_err(to_custom_error::<'de, D, _>)?;
AccountAddress::from_bytes(&value).map_err(to_custom_error::<'de, D, _>)
} else {
<Vec<u8> as Deserialize>::deserialize(deserializer)
R::deserialize_as(deserializer)
}
}
}

impl<'de> DeserializeAs<'de, ByteBuf> for Base64OrDefault {
fn deserialize_as<D>(deserializer: D) -> Result<ByteBuf, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
Ok(ByteBuf::from(
Encoding::Base64
.decode(s)
.map_err(to_custom_error::<'de, D, _>)?,
))
} else {
<ByteBuf as Deserialize>::deserialize(deserializer)
pub mod encoding {
use anyhow::anyhow;
use base64ct::Encoding as _;

pub trait Encoding {
fn decode(s: String) -> Result<Vec<u8>, anyhow::Error>;
fn encode<T: AsRef<[u8]>>(data: T) -> String;
}
pub struct Hex;
pub struct Base64;

impl Encoding for Hex {
fn decode(s: String) -> Result<Vec<u8>, anyhow::Error> {
Ok(hex::decode(s)?)
}

fn encode<T: AsRef<[u8]>>(data: T) -> String {
hex::encode(data)
}
}
impl Encoding for Base64 {
fn decode(s: String) -> Result<Vec<u8>, anyhow::Error> {
base64ct::Base64::decode_vec(&s).map_err(|e| anyhow!(e))
}

fn encode<T: AsRef<[u8]>>(data: T) -> String {
base64ct::Base64::encode_string(data.as_ref())
}
}
}
34 changes: 31 additions & 3 deletions sui_types/src/unit_tests/base_types_tests.rs
Original file line number Diff line number Diff line change
@@ -179,14 +179,28 @@ fn test_object_id_serde_not_human_readable() {
assert_eq!(deserialized, obj_id);
}

#[test]
fn test_object_id_serde_with_expected_value() {
let object_id_vec = vec![
71, 183, 32, 230, 10, 187, 253, 56, 195, 142, 30, 23, 38, 201, 102, 0, 130, 240, 199, 52,
];
let object_id = ObjectID::try_from(object_id_vec.clone()).unwrap();
let json_serialized = serde_json::to_string(&object_id).unwrap();
let bcs_serialized = bcs::to_bytes(&object_id).unwrap();

let expected_json_address = "\"47b720e60abbfd38c38e1e1726c9660082f0c734\"";
assert_eq!(expected_json_address, json_serialized);
assert_eq!(object_id_vec, bcs_serialized);
}

#[test]
fn test_address_serde_not_human_readable() {
let address = SuiAddress::random_for_testing_only();
let serialized = bincode::serialize(&address).unwrap();
let bcs_serialized = bcs::to_bytes(&address).unwrap();
// bincode use 8 bytes for BYTES len and bcs use 1 byte
assert_eq!(serialized[8..], bcs_serialized[1..]);
assert_eq!(address.0, serialized[8..]);
assert_eq!(serialized, bcs_serialized);
assert_eq!(address.0, serialized[..]);
let deserialized: SuiAddress = bincode::deserialize(&serialized).unwrap();
assert_eq!(deserialized, address);
}
@@ -200,6 +214,20 @@ fn test_address_serde_human_readable() {
assert_eq!(deserialized, address);
}

#[test]
fn test_address_serde_with_expected_value() {
let address_vec = vec![
42, 202, 201, 60, 233, 75, 103, 251, 224, 56, 148, 252, 58, 57, 61, 244, 92, 124, 211, 191,
];
let address = SuiAddress::try_from(address_vec.clone()).unwrap();
let json_serialized = serde_json::to_string(&address).unwrap();
let bcs_serialized = bcs::to_bytes(&address).unwrap();

let expected_json_address = "\"2acac93ce94b67fbe03894fc3a393df45c7cd3bf\"";
assert_eq!(expected_json_address, json_serialized);
assert_eq!(address_vec, bcs_serialized);
}

#[test]
fn test_transaction_digest_serde_not_human_readable() {
let digest = TransactionDigest::random();
@@ -268,7 +296,7 @@ fn test_move_object_size_for_gas_metering() {
// all the metadata data needed for serializing various types.
// If the following assertion breaks, it's likely you have changed MoveObject's fields.
// Make sure to adjust `object_size_for_gas_metering()` to include those changes.
assert_eq!(size + 16, serialized.len());
assert_eq!(size + 3, serialized.len());
}

#[test]

0 comments on commit de86f44

Please sign in to comment.