diff --git a/crates/core/src/xdr/codec.rs b/crates/core/src/xdr/codec.rs index f0d6cf2e..ddfb3fbb 100644 --- a/crates/core/src/xdr/codec.rs +++ b/crates/core/src/xdr/codec.rs @@ -3,8 +3,8 @@ use crate::error::{PrismError, PrismResult}; use base64::{engine::general_purpose::STANDARD, Engine as _}; use stellar_xdr::curr::{ - DiagnosticEvent, LedgerEntry, Limits, ReadXdr, ScVec, TransactionEnvelope, TransactionMeta, - WriteXdr, TransactionResult, + DiagnosticEvent, LedgerEntry, Limits, ReadXdr, ScMap, ScMapEntry, ScVec, + TransactionEnvelope, TransactionMeta, WriteXdr, TransactionResult, }; pub trait XdrCodec: Sized { @@ -144,6 +144,44 @@ impl XdrCodec for ScVec { } } +impl XdrCodec for ScMap { + const TYPE_NAME: &'static str = "ScMap"; + + fn from_xdr_bytes(bytes: &[u8]) -> PrismResult { + ScMap::from_xdr(bytes, Limits::none()).map_err(|e| { + PrismError::XdrDecodingFailed { + type_name: Self::TYPE_NAME, + reason: e.to_string(), + } + }) + } + + fn to_xdr_bytes(&self) -> PrismResult> { + self.to_xdr(Limits::none()).map_err(|e| { + PrismError::XdrError(format!("Failed to encode {}: {}", Self::TYPE_NAME, e)) + }) + } +} + +impl XdrCodec for ScMapEntry { + const TYPE_NAME: &'static str = "ScMapEntry"; + + fn from_xdr_bytes(bytes: &[u8]) -> PrismResult { + ScMapEntry::from_xdr(bytes, Limits::none()).map_err(|e| { + PrismError::XdrDecodingFailed { + type_name: Self::TYPE_NAME, + reason: e.to_string(), + } + }) + } + + fn to_xdr_bytes(&self) -> PrismResult> { + self.to_xdr(Limits::none()).map_err(|e| { + PrismError::XdrError(format!("Failed to encode {}: {}", Self::TYPE_NAME, e)) + }) + } +} + /// Decode a base64-encoded XDR string to raw bytes. pub fn decode_xdr_base64(xdr_base64: &str) -> PrismResult> { STANDARD.decode(xdr_base64).map_err(|e| { @@ -191,8 +229,9 @@ fn hex_decode(input: &str) -> Result, String> { mod tests { use super::*; use stellar_xdr::curr::{ - ExtensionPoint, Memo, MuxedAccount, OperationMeta, Preconditions, SequenceNumber, - Transaction, TransactionExt, TransactionMetaV3, TransactionV1Envelope, Uint256, + ExtensionPoint, Memo, MuxedAccount, OperationMeta, Preconditions, ScMapEntry, ScVal, + SequenceNumber, Transaction, TransactionExt, TransactionMetaV3, TransactionV1Envelope, + Uint256, }; fn make_test_envelope() -> TransactionEnvelope { @@ -306,4 +345,59 @@ mod tests { let decoded = ::from_xdr_base64(&b64).expect("decode"); assert_eq!(scvec, decoded); } + + #[test] + fn test_scmap_empty_round_trip() { + let map = ScMap(vec![].try_into().unwrap()); + let b64 = crate::xdr::codec::XdrCodec::to_xdr_base64(&map).expect("encode"); + let decoded = ::from_xdr_base64(&b64).expect("decode"); + assert_eq!(map, decoded); + assert_eq!(decoded.0.len(), 0); + } + + #[test] + fn test_scmap_single_entry_round_trip() { + let entry = ScMapEntry { + key: ScVal::U32(1), + val: ScVal::Bool(true), + }; + // ScMapEntry round-trip + let b64 = crate::xdr::codec::XdrCodec::to_xdr_base64(&entry).expect("encode"); + let decoded_entry = ::from_xdr_base64(&b64).expect("decode"); + assert_eq!(entry, decoded_entry); + + // ScMap with single entry + let map = ScMap::sorted_from_entries(vec![ScMapEntry { + key: ScVal::U32(1), + val: ScVal::Bool(true), + }].into_iter()).expect("sorted_from_entries"); + let b64 = crate::xdr::codec::XdrCodec::to_xdr_base64(&map).expect("encode"); + let decoded = ::from_xdr_base64(&b64).expect("decode"); + assert_eq!(map, decoded); + assert_eq!(decoded.0.len(), 1); + assert_eq!(decoded.0[0].key, ScVal::U32(1)); + assert_eq!(decoded.0[0].val, ScVal::Bool(true)); + } + + #[test] + fn test_scmap_multi_entry_round_trip() { + // Entries inserted out of order — sorted_from_entries must sort them by key. + let entries = vec![ + ScMapEntry { key: ScVal::U32(3), val: ScVal::Bool(false) }, + ScMapEntry { key: ScVal::U32(1), val: ScVal::Void }, + ScMapEntry { key: ScVal::U32(2), val: ScVal::Bool(true) }, + ]; + let map = ScMap::sorted_from_entries(entries.into_iter()).expect("sorted_from_entries"); + let b64 = crate::xdr::codec::XdrCodec::to_xdr_base64(&map).expect("encode"); + let decoded = ::from_xdr_base64(&b64).expect("decode"); + assert_eq!(map, decoded); + assert_eq!(decoded.0.len(), 3); + // Keys must be in ascending order after sorting. + assert_eq!(decoded.0[0].key, ScVal::U32(1)); + assert_eq!(decoded.0[1].key, ScVal::U32(2)); + assert_eq!(decoded.0[2].key, ScVal::U32(3)); + assert_eq!(decoded.0[0].val, ScVal::Void); + assert_eq!(decoded.0[1].val, ScVal::Bool(true)); + assert_eq!(decoded.0[2].val, ScVal::Bool(false)); + } }