From 204ccb8341c3b918f4223327af2522ecd50743a7 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 14 May 2024 11:04:32 +1000 Subject: [PATCH] Encode claimable balance ids to match Horizon in JSON (#368) * fix claimable balance id render * Encode claimable balance ids to match Horizon in JSON * make str continue to work with alloc * make str continue to work with alloc --- Makefile | 4 ++-- src/curr/generated.rs | 3 +-- src/curr/str.rs | 45 +++++++++++++++++++++++++++++++++++++++++-- src/next/generated.rs | 3 +-- src/next/str.rs | 45 +++++++++++++++++++++++++++++++++++++++++-- tests/str.rs | 26 +++++++++++++++++++++++-- 6 files changed, 114 insertions(+), 12 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/curr/str.rs b/src/curr/str.rs index b5641d6c..c8fe36cb 100644 --- a/src/curr/str.rs +++ b/src/curr/str.rs @@ -18,11 +18,15 @@ //# - AssetCode //# - AssetCode4 //# - AssetCode12 +//# +//# ## Other +//# - ClaimableBalanceId #![cfg(feature = "alloc")] use super::{ - 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, }; impl From for Error { @@ -336,3 +340,40 @@ 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( + (&bytes[4..]).try_into()?, + ))), + _ => Err(Error::Invalid), + } + } +} + +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] { + write!(f, "{b:02x}")?; + } + for b in bytes { + write!(f, "{b:02x}")?; + } + } + } + Ok(()) + } +} 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 { diff --git a/src/next/str.rs b/src/next/str.rs index c00d65bb..098dd4e3 100644 --- a/src/next/str.rs +++ b/src/next/str.rs @@ -18,11 +18,15 @@ //# - AssetCode //# - AssetCode4 //# - AssetCode12 +//# +//# ## Other +//# - ClaimableBalanceId #![cfg(feature = "alloc")] use super::{ - 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, }; impl From for Error { @@ -325,3 +329,40 @@ 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( + (&bytes[4..]).try_into()?, + ))), + _ => Err(Error::Invalid), + } + } +} + +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] { + write!(f, "{b:02x}")?; + } + for b in bytes { + write!(f, "{b:02x}")?; + } + } + } + Ok(()) + } +} diff --git a/tests/str.rs b/tests/str.rs index 7d8eb63d..4dafa23d 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::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::LengthMismatch)); + // Unrecognized discriminant value. + assert_eq!(ClaimableBalanceId::from_str("000000010101010101010101010101010101010101010101010101010101010101010101"), Err(Error::Invalid)); +}