From 72fa91b891185792ee4b66eac131295d399c7cdb Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 13 May 2024 11:09:47 +1000 Subject: [PATCH 1/4] fix claimable balance id render --- Makefile | 4 ++-- src/curr/generated.rs | 3 +-- src/next/generated.rs | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2e14d4f8..83d2c260 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ CARGO_HACK_ARGS=--feature-powerset --exclude-features default --group-features b CARGO_DOC_ARGS?=--open XDRGEN_VERSION=e2cac557162d99b12ae73b846cf3d5bfe16636de -XDRGEN_TYPES_CUSTOM_STR_IMPL_CURR=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12 -XDRGEN_TYPES_CUSTOM_STR_IMPL_NEXT=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12 +XDRGEN_TYPES_CUSTOM_STR_IMPL_CURR=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12,ClaimableBalanceId +XDRGEN_TYPES_CUSTOM_STR_IMPL_NEXT=PublicKey,AccountId,MuxedAccount,MuxedAccountMed25519,SignerKey,SignerKeyEd25519SignedPayload,NodeId,ScAddress,AssetCode,AssetCode4,AssetCode12,ClaimableBalanceId all: build test diff --git a/src/curr/generated.rs b/src/curr/generated.rs index deabc26b..9d9e71e2 100644 --- a/src/curr/generated.rs +++ b/src/curr/generated.rs @@ -14504,8 +14504,7 @@ impl WriteXdr for ClaimableBalanceIdType { #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[cfg_attr( all(feature = "serde", feature = "alloc"), - derive(serde::Serialize, serde::Deserialize), - serde(rename_all = "snake_case") + derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr) )] #[allow(clippy::large_enum_variant)] pub enum ClaimableBalanceId { diff --git a/src/next/generated.rs b/src/next/generated.rs index 5e1bb265..75ae9973 100644 --- a/src/next/generated.rs +++ b/src/next/generated.rs @@ -14504,8 +14504,7 @@ impl WriteXdr for ClaimableBalanceIdType { #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[cfg_attr( all(feature = "serde", feature = "alloc"), - derive(serde::Serialize, serde::Deserialize), - serde(rename_all = "snake_case") + derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr) )] #[allow(clippy::large_enum_variant)] pub enum ClaimableBalanceId { From 31890cfd94e2bf7c10e34b6afac87ffdb7ea8b46 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 13 May 2024 13:23:20 +1000 Subject: [PATCH 2/4] Encode claimable balance ids to match Horizon in JSON --- src/curr/str.rs | 39 ++++++++++++++++++++++++++++++++++++--- src/next/str.rs | 39 ++++++++++++++++++++++++++++++++++++--- tests/str.rs | 26 ++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/curr/str.rs b/src/curr/str.rs index b5641d6c..13c463f7 100644 --- a/src/curr/str.rs +++ b/src/curr/str.rs @@ -18,11 +18,15 @@ //# - AssetCode //# - AssetCode4 //# - AssetCode12 -#![cfg(feature = "alloc")] +//# +//# ## Other +//# - ClaimableBalanceId +#![cfg(feature = "std")] use super::{ - AccountId, AssetCode, AssetCode12, AssetCode4, Error, Hash, MuxedAccount, MuxedAccountMed25519, - NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, Uint256, + AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, Limits, + MuxedAccount, MuxedAccountMed25519, NodeId, PublicKey, ReadXdr, ScAddress, SignerKey, + SignerKeyEd25519SignedPayload, Uint256, WriteXdr, }; impl From for Error { @@ -336,3 +340,32 @@ impl core::fmt::Display for AssetCode { } } } + +impl core::str::FromStr for ClaimableBalanceId { + type Err = Error; + fn from_str(s: &str) -> core::result::Result { + let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?; + ClaimableBalanceId::from_xdr( + bytes, + // No limit is safe for encoding ClaimableBalanceId as the type is a + // fixed size type. + Limits::none(), + ) + } +} + +impl core::fmt::Display for ClaimableBalanceId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let bytes = self + .to_xdr( + // No limit is safe for encoding ClaimableBalanceId as the type is a + // fixed size type. + Limits::none(), + ) + .map_err(|_| core::fmt::Error)?; + for b in bytes { + write!(f, "{b:02x}")?; + } + Ok(()) + } +} diff --git a/src/next/str.rs b/src/next/str.rs index c00d65bb..047a3ece 100644 --- a/src/next/str.rs +++ b/src/next/str.rs @@ -18,11 +18,15 @@ //# - AssetCode //# - AssetCode4 //# - AssetCode12 -#![cfg(feature = "alloc")] +//# +//# ## Other +//# - ClaimableBalanceId +#![cfg(feature = "std")] use super::{ - AccountId, AssetCode, AssetCode12, AssetCode4, Error, Hash, MuxedAccount, MuxedAccountMed25519, - NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, Uint256, + AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, Limits, + MuxedAccount, MuxedAccountMed25519, NodeId, PublicKey, ReadXdr, ScAddress, SignerKey, + SignerKeyEd25519SignedPayload, Uint256, WriteXdr, }; impl From for Error { @@ -325,3 +329,32 @@ impl core::fmt::Display for AssetCode { } } } + +impl core::str::FromStr for ClaimableBalanceId { + type Err = Error; + fn from_str(s: &str) -> core::result::Result { + let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?; + ClaimableBalanceId::from_xdr( + bytes, + // No limit is safe for encoding ClaimableBalanceId as the type is a + // fixed size type. + Limits::none(), + ) + } +} + +impl core::fmt::Display for ClaimableBalanceId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let bytes = self + .to_xdr( + // No limit is safe for encoding ClaimableBalanceId as the type is a + // fixed size type. + Limits::none(), + ) + .map_err(|_| core::fmt::Error)?; + for b in bytes { + write!(f, "{b:02x}")?; + } + Ok(()) + } +} diff --git a/tests/str.rs b/tests/str.rs index 7d8eb63d..7b260db6 100644 --- a/tests/str.rs +++ b/tests/str.rs @@ -4,8 +4,9 @@ use stellar_xdr::curr as stellar_xdr; use stellar_xdr::{ - AccountId, AssetCode, AssetCode12, AssetCode4, Error, Hash, MuxedAccount, MuxedAccountMed25519, - NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, Uint256, + AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, MuxedAccount, + MuxedAccountMed25519, NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, + Uint256, }; use std::str::FromStr; @@ -541,3 +542,24 @@ fn asset_code_from_str_to_string_roundtrip_unicode() { assert_eq!(AssetCode::CreditAlphanum4(AssetCode4(*b"a\xc3\xc3d")).to_string(), r"a\xc3\xc3d"); assert_eq!(AssetCode::from_str(r"a\xc3\xc3d"), Ok(AssetCode::CreditAlphanum4(AssetCode4(*b"a\xc3\xc3d")))); } + +#[test] +#[rustfmt::skip] +fn claimable_balance_id() { + assert_eq!( + ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash([1u8; 32])).to_string(), + "000000000101010101010101010101010101010101010101010101010101010101010101", + ); + // Valid + assert_eq!(ClaimableBalanceId::from_str("000000000101010101010101010101010101010101010101010101010101010101010101"), Ok(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash([1u8; 32])))); + // Half byte short. + assert_eq!(ClaimableBalanceId::from_str("00000000010101010101010101010101010101010101010101010101010101010101010"), Err(Error::InvalidHex)); + // Full byte short. + assert_eq!(ClaimableBalanceId::from_str("0000000001010101010101010101010101010101010101010101010101010101010101"), Err(Error::Io(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "failed to fill whole buffer")))); + // Half byte too long. + assert_eq!(ClaimableBalanceId::from_str("0000000001010101010101010101010101010101010101010101010101010101010101011"), Err(Error::InvalidHex)); + // Full byte too long. + assert_eq!(ClaimableBalanceId::from_str("00000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::Invalid)); + // Unrecognized discriminant value. + assert_eq!(ClaimableBalanceId::from_str("000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::Invalid)); +} From c1b62dd70f383983401455593667c0c61110bab9 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 13 May 2024 13:34:42 +1000 Subject: [PATCH 3/4] make str continue to work with alloc --- src/curr/str.rs | 38 +++++++++++++++++++------------------- tests/str.rs | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/curr/str.rs b/src/curr/str.rs index 13c463f7..0a5936fb 100644 --- a/src/curr/str.rs +++ b/src/curr/str.rs @@ -21,12 +21,12 @@ //# //# ## Other //# - ClaimableBalanceId -#![cfg(feature = "std")] +#![cfg(feature = "alloc")] use super::{ - AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, Limits, - MuxedAccount, MuxedAccountMed25519, NodeId, PublicKey, ReadXdr, ScAddress, SignerKey, - SignerKeyEd25519SignedPayload, Uint256, WriteXdr, + AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, MuxedAccount, + MuxedAccountMed25519, NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, + Uint256, }; impl From for Error { @@ -345,26 +345,26 @@ impl core::str::FromStr for ClaimableBalanceId { type Err = Error; fn from_str(s: &str) -> core::result::Result { let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?; - ClaimableBalanceId::from_xdr( - bytes, - // No limit is safe for encoding ClaimableBalanceId as the type is a - // fixed size type. - Limits::none(), - ) + match bytes.as_slice() { + [0, 0, 0, 0, ..] => Ok(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash( + (&bytes[4..]).try_into()?, + ))), + _ => Err(Error::Invalid), + } } } impl core::fmt::Display for ClaimableBalanceId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let bytes = self - .to_xdr( - // No limit is safe for encoding ClaimableBalanceId as the type is a - // fixed size type. - Limits::none(), - ) - .map_err(|_| core::fmt::Error)?; - for b in bytes { - write!(f, "{b:02x}")?; + match self { + ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(bytes)) => { + for b in [0u8, 0, 0, 0] { + write!(f, "{b:02x}")?; + } + for b in bytes { + write!(f, "{b:02x}")?; + } + } } Ok(()) } diff --git a/tests/str.rs b/tests/str.rs index 7b260db6..4dafa23d 100644 --- a/tests/str.rs +++ b/tests/str.rs @@ -555,11 +555,11 @@ fn claimable_balance_id() { // Half byte short. assert_eq!(ClaimableBalanceId::from_str("00000000010101010101010101010101010101010101010101010101010101010101010"), Err(Error::InvalidHex)); // Full byte short. - assert_eq!(ClaimableBalanceId::from_str("0000000001010101010101010101010101010101010101010101010101010101010101"), Err(Error::Io(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "failed to fill whole buffer")))); + assert_eq!(ClaimableBalanceId::from_str("0000000001010101010101010101010101010101010101010101010101010101010101"), Err(Error::LengthMismatch)); // Half byte too long. assert_eq!(ClaimableBalanceId::from_str("0000000001010101010101010101010101010101010101010101010101010101010101011"), Err(Error::InvalidHex)); // Full byte too long. - assert_eq!(ClaimableBalanceId::from_str("00000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::Invalid)); + assert_eq!(ClaimableBalanceId::from_str("00000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::LengthMismatch)); // Unrecognized discriminant value. assert_eq!(ClaimableBalanceId::from_str("000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::Invalid)); } From 4c2042392666b4c96ce9a726a07a539342975492 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 13 May 2024 13:37:24 +1000 Subject: [PATCH 4/4] make str continue to work with alloc --- src/curr/str.rs | 8 ++++++++ src/next/str.rs | 46 +++++++++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/curr/str.rs b/src/curr/str.rs index 0a5936fb..c8fe36cb 100644 --- a/src/curr/str.rs +++ b/src/curr/str.rs @@ -344,6 +344,10 @@ impl core::fmt::Display for AssetCode { impl core::str::FromStr for ClaimableBalanceId { type Err = Error; fn from_str(s: &str) -> core::result::Result { + // This conversion to a hex string could be done by XDR encoding the + // self value, but because XDR encoding requires the std feature, this + // approach is taken instead to preserve the fact that the serde feature + // is available with alloc only. let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?; match bytes.as_slice() { [0, 0, 0, 0, ..] => Ok(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash( @@ -356,6 +360,10 @@ impl core::str::FromStr for ClaimableBalanceId { impl core::fmt::Display for ClaimableBalanceId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // This conversion from a hex string could be done by XDR decoding the + // self value, but because XDR decoding requires the std feature, this + // approach is taken instead to preserve the fact that the serde feature + // is available with alloc only. match self { ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(bytes)) => { for b in [0u8, 0, 0, 0] { diff --git a/src/next/str.rs b/src/next/str.rs index 047a3ece..098dd4e3 100644 --- a/src/next/str.rs +++ b/src/next/str.rs @@ -21,12 +21,12 @@ //# //# ## Other //# - ClaimableBalanceId -#![cfg(feature = "std")] +#![cfg(feature = "alloc")] use super::{ - AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, Limits, - MuxedAccount, MuxedAccountMed25519, NodeId, PublicKey, ReadXdr, ScAddress, SignerKey, - SignerKeyEd25519SignedPayload, Uint256, WriteXdr, + AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, Error, Hash, MuxedAccount, + MuxedAccountMed25519, NodeId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, + Uint256, }; impl From for Error { @@ -333,27 +333,35 @@ impl core::fmt::Display for AssetCode { impl core::str::FromStr for ClaimableBalanceId { type Err = Error; fn from_str(s: &str) -> core::result::Result { + // This conversion to a hex string could be done by XDR encoding the + // self value, but because XDR encoding requires the std feature, this + // approach is taken instead to preserve the fact that the serde feature + // is available with alloc only. let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?; - ClaimableBalanceId::from_xdr( - bytes, - // No limit is safe for encoding ClaimableBalanceId as the type is a - // fixed size type. - Limits::none(), - ) + match bytes.as_slice() { + [0, 0, 0, 0, ..] => Ok(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash( + (&bytes[4..]).try_into()?, + ))), + _ => Err(Error::Invalid), + } } } impl core::fmt::Display for ClaimableBalanceId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let bytes = self - .to_xdr( - // No limit is safe for encoding ClaimableBalanceId as the type is a - // fixed size type. - Limits::none(), - ) - .map_err(|_| core::fmt::Error)?; - for b in bytes { - write!(f, "{b:02x}")?; + // This conversion from a hex string could be done by XDR decoding the + // self value, but because XDR decoding requires the std feature, this + // approach is taken instead to preserve the fact that the serde feature + // is available with alloc only. + match self { + ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(bytes)) => { + for b in [0u8, 0, 0, 0] { + write!(f, "{b:02x}")?; + } + for b in bytes { + write!(f, "{b:02x}")?; + } + } } Ok(()) }