diff --git a/.pnp.cjs b/.pnp.cjs index 1f0934b2577..acb4ac603a3 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3076,7 +3076,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/dashcore-lib", "npm:0.22.0"],\ ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ - ["@types/bs58", "npm:4.0.1"],\ + ["@types/bs58", "npm:4.0.4"],\ ["@types/node", "npm:14.17.34"],\ ["@yarnpkg/pnpify", "npm:4.0.0-rc.42"],\ ["ajv", "npm:8.12.0"],\ @@ -3131,6 +3131,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@dashevo/wasm-dpp2", "workspace:packages/wasm-dpp2"],\ ["assert", "npm:2.0.0"],\ + ["bs58", "npm:4.0.1"],\ ["buffer", "npm:6.0.3"],\ ["chai", "npm:4.3.10"],\ ["chai-as-promised", "virtual:e2d057e7cc143d3cb9bec864f4a2d862441b5a09f81f8e6c46e7a098cbc89e4d07017cc6e2e2142d5704bb55da853cbec2d025ebc0b30e8696c31380c00f2c7d#npm:7.1.1"],\ @@ -4629,10 +4630,11 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@types/bs58", [\ - ["npm:4.0.1", {\ - "packageLocation": "./.yarn/cache/@types-bs58-npm-4.0.1-179273a650-5063fed6bb.zip/node_modules/@types/bs58/",\ + ["npm:4.0.4", {\ + "packageLocation": "./.yarn/cache/@types-bs58-npm-4.0.4-61d579b54c-9cac5a0034.zip/node_modules/@types/bs58/",\ "packageDependencies": [\ - ["@types/bs58", "npm:4.0.1"],\ + ["@types/bs58", "npm:4.0.4"],\ + ["@types/node", "npm:18.16.1"],\ ["base-x", "npm:3.0.11"]\ ],\ "linkType": "HARD"\ diff --git a/.yarn/cache/@types-bs58-npm-4.0.1-179273a650-5063fed6bb.zip b/.yarn/cache/@types-bs58-npm-4.0.1-179273a650-5063fed6bb.zip deleted file mode 100644 index 1beab581df0..00000000000 Binary files a/.yarn/cache/@types-bs58-npm-4.0.1-179273a650-5063fed6bb.zip and /dev/null differ diff --git a/.yarn/cache/@types-bs58-npm-4.0.4-61d579b54c-9cac5a0034.zip b/.yarn/cache/@types-bs58-npm-4.0.4-61d579b54c-9cac5a0034.zip new file mode 100644 index 00000000000..95c2d4a6c49 Binary files /dev/null and b/.yarn/cache/@types-bs58-npm-4.0.4-61d579b54c-9cac5a0034.zip differ diff --git a/Cargo.lock b/Cargo.lock index 9974e179528..f59debe9be8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5604,6 +5604,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "git+https://github.com/dashpay/serde-wasm-bindgen?branch=fix%2Fuint8array-to-bytes#c909c5759c9ca997abe96283edd8e23cb64b4ce0" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -7245,7 +7255,7 @@ dependencies = [ "num_enum 0.7.4", "paste", "serde", - "serde-wasm-bindgen 0.5.0", + "serde-wasm-bindgen 0.5.0 (git+https://github.com/QuantumExplorer/serde-wasm-bindgen?branch=feat%2Fnot_human_readable)", "serde_json", "thiserror 2.0.17", "wasm-bindgen", @@ -7256,7 +7266,7 @@ dependencies = [ [[package]] name = "wasm-dpp2" -version = "1.0.5" +version = "3.0.0-dev.5" dependencies = [ "anyhow", "bincode 2.0.0-rc.3", @@ -7264,7 +7274,7 @@ dependencies = [ "hex", "js-sys", "serde", - "serde-wasm-bindgen 0.5.0", + "serde-wasm-bindgen 0.5.0 (git+https://github.com/dashpay/serde-wasm-bindgen?branch=fix%2Fuint8array-to-bytes)", "serde_json", "sha2", "thiserror 1.0.69", @@ -7325,7 +7335,7 @@ dependencies = [ "rs-dapi-client", "rs-sdk-trusted-context-provider", "serde", - "serde-wasm-bindgen 0.6.5", + "serde-wasm-bindgen 0.5.0 (git+https://github.com/dashpay/serde-wasm-bindgen?branch=fix%2Fuint8array-to-bytes)", "serde_json", "sha2", "simple-signer", diff --git a/packages/js-evo-sdk/src/dpns/facade.ts b/packages/js-evo-sdk/src/dpns/facade.ts index d2daa0c8fcf..5f39b83a1dd 100644 --- a/packages/js-evo-sdk/src/dpns/facade.ts +++ b/packages/js-evo-sdk/src/dpns/facade.ts @@ -49,12 +49,12 @@ export class DpnsFacade { return w.getDpnsUsername(identityId); } - async usernamesWithProof(query: wasm.DpnsUsernamesQuery): Promise { + async usernamesWithProof(query: wasm.DpnsUsernamesQuery): Promise>> { const w = await this.sdk.getWasmSdkConnected(); return w.getDpnsUsernamesWithProofInfo(query); } - async usernameWithProof(identityId: wasm.IdentifierLike): Promise { + async usernameWithProof(identityId: wasm.IdentifierLike): Promise> { const w = await this.sdk.getWasmSdkConnected(); return w.getDpnsUsernameWithProofInfo(identityId); } diff --git a/packages/js-evo-sdk/src/group/facade.ts b/packages/js-evo-sdk/src/group/facade.ts index fa6338b0a07..1717171d961 100644 --- a/packages/js-evo-sdk/src/group/facade.ts +++ b/packages/js-evo-sdk/src/group/facade.ts @@ -85,12 +85,12 @@ export class GroupFacade { return w.getContestedResourcesWithProofInfo(query); } - async contestedResourceVotersForIdentity(query: wasm.ContestedResourceVotersForIdentityQuery): Promise { + async contestedResourceVotersForIdentity(query: wasm.ContestedResourceVotersForIdentityQuery): Promise { const w = await this.sdk.getWasmSdkConnected(); return w.getContestedResourceVotersForIdentity(query); } - async contestedResourceVotersForIdentityWithProof(query: wasm.ContestedResourceVotersForIdentityQuery): Promise> { + async contestedResourceVotersForIdentityWithProof(query: wasm.ContestedResourceVotersForIdentityQuery): Promise> { const w = await this.sdk.getWasmSdkConnected(); return w.getContestedResourceVotersForIdentityWithProofInfo(query); } diff --git a/packages/js-evo-sdk/src/voting/facade.ts b/packages/js-evo-sdk/src/voting/facade.ts index 4f3ae8c3629..970326a50d7 100644 --- a/packages/js-evo-sdk/src/voting/facade.ts +++ b/packages/js-evo-sdk/src/voting/facade.ts @@ -16,12 +16,12 @@ export class VotingFacade { return w.getContestedResourceVoteStateWithProofInfo(query); } - async contestedResourceIdentityVotes(query: wasm.ContestedResourceIdentityVotesQuery): Promise { + async contestedResourceIdentityVotes(query: wasm.ContestedResourceIdentityVotesQuery): Promise> { const w = await this.sdk.getWasmSdkConnected(); return w.getContestedResourceIdentityVotes(query); } - async contestedResourceIdentityVotesWithProof(query: wasm.ContestedResourceIdentityVotesQuery): Promise }>> { + async contestedResourceIdentityVotesWithProof(query: wasm.ContestedResourceIdentityVotesQuery): Promise>> { const w = await this.sdk.getWasmSdkConnected(); return w.getContestedResourceIdentityVotesWithProofInfo(query); } diff --git a/packages/rs-dpp/src/document/extended_document/v0/json_conversion.rs b/packages/rs-dpp/src/document/extended_document/v0/json_conversion.rs index cbc8cb4a266..1db82cf214b 100644 --- a/packages/rs-dpp/src/document/extended_document/v0/json_conversion.rs +++ b/packages/rs-dpp/src/document/extended_document/v0/json_conversion.rs @@ -42,12 +42,13 @@ impl DocumentJsonMethodsV0<'_> for ExtendedDocumentV0 { Ok(json) } - fn from_json_value( + fn from_json_value( document_value: JsonValue, platform_version: &PlatformVersion, ) -> Result where - for<'de> S: Deserialize<'de> + TryInto, + for<'de> S: Deserialize<'de> + TryInto, + E: Into, { Self::from_platform_value(document_value.into(), platform_version) } diff --git a/packages/rs-dpp/src/document/serialization_traits/json_conversion/mod.rs b/packages/rs-dpp/src/document/serialization_traits/json_conversion/mod.rs index f2b168ad698..6d440f55c56 100644 --- a/packages/rs-dpp/src/document/serialization_traits/json_conversion/mod.rs +++ b/packages/rs-dpp/src/document/serialization_traits/json_conversion/mod.rs @@ -29,19 +29,20 @@ impl DocumentJsonMethodsV0<'_> for Document { } /// Create a document from a JSON value. - fn from_json_value( + fn from_json_value( document_value: JsonValue, platform_version: &PlatformVersion, ) -> Result where - for<'de> S: Deserialize<'de> + TryInto, + for<'de> S: Deserialize<'de> + TryInto, + E: Into, { match platform_version .dpp .document_versions .document_structure_version { - 0 => Ok(Document::V0(DocumentV0::from_json_value::( + 0 => Ok(Document::V0(DocumentV0::from_json_value::( document_value, platform_version, )?)), diff --git a/packages/rs-dpp/src/document/serialization_traits/json_conversion/v0/mod.rs b/packages/rs-dpp/src/document/serialization_traits/json_conversion/v0/mod.rs index 2fff0a43370..ed7254d48d2 100644 --- a/packages/rs-dpp/src/document/serialization_traits/json_conversion/v0/mod.rs +++ b/packages/rs-dpp/src/document/serialization_traits/json_conversion/v0/mod.rs @@ -12,11 +12,12 @@ pub trait DocumentJsonMethodsV0<'a>: DocumentPlatformValueMethodsV0<'a> { platform_version: &PlatformVersion, ) -> Result; fn to_json(&self, platform_version: &PlatformVersion) -> Result; - fn from_json_value( + fn from_json_value( document_value: JsonValue, platform_version: &PlatformVersion, ) -> Result where - for<'de> S: Deserialize<'de> + TryInto, + for<'de> S: Deserialize<'de> + TryInto, + E: Into, Self: Sized; } diff --git a/packages/rs-dpp/src/document/v0/json_conversion.rs b/packages/rs-dpp/src/document/v0/json_conversion.rs index f05c6d72d3b..0ade5bbead0 100644 --- a/packages/rs-dpp/src/document/v0/json_conversion.rs +++ b/packages/rs-dpp/src/document/v0/json_conversion.rs @@ -104,24 +104,29 @@ impl DocumentJsonMethodsV0<'_> for DocumentV0 { .map(|v| v.try_into().map_err(ProtocolError::ValueError))? } - fn from_json_value( + fn from_json_value( mut document_value: JsonValue, _platform_version: &PlatformVersion, ) -> Result where - for<'de> S: Deserialize<'de> + TryInto, + for<'de> S: Deserialize<'de> + TryInto, + E: Into, { let mut document = Self { ..Default::default() }; if let Ok(value) = document_value.remove(property_names::ID) { - let data: S = serde_json::from_value(value)?; - document.id = data.try_into()?; + if !value.is_null() { + let data: S = serde_json::from_value(value)?; + document.id = data.try_into().map_err(Into::into)?; + } } if let Ok(value) = document_value.remove(property_names::OWNER_ID) { - let data: S = serde_json::from_value(value)?; - document.owner_id = data.try_into()?; + if !value.is_null() { + let data: S = serde_json::from_value(value)?; + document.owner_id = data.try_into().map_err(Into::into)?; + } } if let Ok(value) = document_value.remove(property_names::REVISION) { document.revision = serde_json::from_value(value)? @@ -154,8 +159,10 @@ impl DocumentJsonMethodsV0<'_> for DocumentV0 { document.transferred_at_core_block_height = serde_json::from_value(value)?; } if let Ok(value) = document_value.remove(property_names::CREATOR_ID) { - let data: S = serde_json::from_value(value)?; - document.creator_id = Some(data.try_into()?); + if !value.is_null() { + let data: S = serde_json::from_value(value)?; + document.creator_id = Some(data.try_into().map_err(Into::into)?); + } } let platform_value: Value = document_value.into(); diff --git a/packages/rs-dpp/src/group/action_event.rs b/packages/rs-dpp/src/group/action_event.rs index d9540ab291f..7e90a5ff9a2 100644 --- a/packages/rs-dpp/src/group/action_event.rs +++ b/packages/rs-dpp/src/group/action_event.rs @@ -2,10 +2,16 @@ use crate::tokens::token_event::TokenEvent; use crate::ProtocolError; use bincode::{Decode, Encode}; use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; #[derive( Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize, )] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize) +)] #[platform_serialize(unversioned)] //versioned directly, no need to use platform_version pub enum GroupActionEvent { TokenEvent(TokenEvent), diff --git a/packages/rs-dpp/src/group/group_action/mod.rs b/packages/rs-dpp/src/group/group_action/mod.rs index 41a4a67060c..0cc81925a34 100644 --- a/packages/rs-dpp/src/group/group_action/mod.rs +++ b/packages/rs-dpp/src/group/group_action/mod.rs @@ -7,10 +7,16 @@ use crate::ProtocolError; use bincode::{Decode, Encode}; use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; use platform_value::Identifier; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; #[derive( Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize, )] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize) +)] #[platform_serialize(unversioned)] //versioned directly, no need to use platform_version pub enum GroupAction { V0(GroupActionV0), diff --git a/packages/rs-dpp/src/group/group_action/v0/mod.rs b/packages/rs-dpp/src/group/group_action/v0/mod.rs index a2c5d89175a..254def487a2 100644 --- a/packages/rs-dpp/src/group/group_action/v0/mod.rs +++ b/packages/rs-dpp/src/group/group_action/v0/mod.rs @@ -5,10 +5,16 @@ use crate::ProtocolError; use bincode::{Decode, Encode}; use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; use platform_value::Identifier; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; #[derive( Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize, )] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize) +)] #[platform_serialize(unversioned)] //versioned directly, no need to use platform_version pub struct GroupActionV0 { pub contract_id: Identifier, diff --git a/packages/rs-dpp/src/identity/identity_public_key/conversion/json/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/conversion/json/mod.rs index c0000c8b0d4..69822ce5a9c 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/conversion/json/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/conversion/json/mod.rs @@ -1,2 +1,44 @@ mod v0; -pub use v0::*; +use crate::identity::identity_public_key::v0::IdentityPublicKeyV0; +use crate::identity::IdentityPublicKey; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use serde_json::Value as JsonValue; +pub use v0::IdentityPublicKeyJsonConversionMethodsV0; + +impl IdentityPublicKeyJsonConversionMethodsV0 for IdentityPublicKey { + fn to_json(&self) -> Result { + match self { + IdentityPublicKey::V0(key) => key.to_json(), + } + } + + fn to_json_object(&self) -> Result { + match self { + IdentityPublicKey::V0(key) => key.to_json_object(), + } + } + + fn from_json_object( + raw_object: JsonValue, + platform_version: &PlatformVersion, + ) -> Result + where + Self: Sized, + { + match platform_version + .dpp + .identity_versions + .identity_key_structure_version + { + 0 => { + IdentityPublicKeyV0::from_json_object(raw_object, platform_version).map(Into::into) + } + version => Err(ProtocolError::UnknownVersionMismatch { + method: "IdentityPublicKey::from_json_object".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/identity/identity_public_key/conversion/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/conversion/mod.rs index 39fce435cae..52b10ca91c6 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/conversion/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/conversion/mod.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "cbor")] +#[cfg(feature = "identity-cbor-conversion")] pub mod cbor; -#[cfg(feature = "json-object")] +#[cfg(feature = "identity-json-conversion")] pub mod json; -#[cfg(feature = "platform-value")] +#[cfg(feature = "identity-value-conversion")] pub mod platform_value; diff --git a/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/mod.rs index 6b680c64f0b..1d8770fa093 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/mod.rs @@ -1,6 +1,48 @@ mod v0; +use crate::identity::identity_public_key::v0::IdentityPublicKeyV0; use crate::identity::IdentityPublicKey; use crate::serialization::ValueConvertible; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::Value; pub use v0::*; impl ValueConvertible<'_> for IdentityPublicKey {} + +impl IdentityPublicKeyPlatformValueConversionMethodsV0 for IdentityPublicKey { + fn to_object(&self) -> Result { + match self { + IdentityPublicKey::V0(key) => key.to_object(), + } + } + + fn to_cleaned_object(&self) -> Result { + match self { + IdentityPublicKey::V0(key) => key.to_cleaned_object(), + } + } + + fn into_object(self) -> Result { + match self { + IdentityPublicKey::V0(key) => key.into_object(), + } + } + + fn from_object( + value: Value, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .identity_versions + .identity_key_structure_version + { + 0 => IdentityPublicKeyV0::from_object(value, platform_version).map(Into::into), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "IdentityPublicKey::from_object".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/v0/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/v0/mod.rs index 47ee14ddb7f..5e98150aee2 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/v0/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/conversion/platform_value/v0/mod.rs @@ -1,4 +1,3 @@ -use crate::identity::identity_public_key::v0::IdentityPublicKeyV0; use crate::version::PlatformVersion; use crate::ProtocolError; use platform_value::Value; @@ -7,8 +6,7 @@ pub trait IdentityPublicKeyPlatformValueConversionMethodsV0 { fn to_object(&self) -> Result; fn to_cleaned_object(&self) -> Result; fn into_object(self) -> Result; - fn from_object( - value: Value, - platform_version: &PlatformVersion, - ) -> Result; + fn from_object(value: Value, platform_version: &PlatformVersion) -> Result + where + Self: Sized; } diff --git a/packages/rs-dpp/src/identity/identity_public_key/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/mod.rs index 87ded0f5d51..d6e60dbb771 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/mod.rs @@ -13,7 +13,7 @@ pub use key_type::KeyType; pub use purpose::Purpose; pub use security_level::SecurityLevel; pub mod accessors; -pub(crate) mod conversion; +pub mod conversion; pub mod fields; pub mod v0; use crate::version::PlatformVersion; diff --git a/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/mod.rs index 2286f418a86..f5e6fe664cb 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/mod.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "cbor")] +#[cfg(feature = "identity-cbor-conversion")] mod cbor; -#[cfg(feature = "json-object")] +#[cfg(feature = "identity-json-conversion")] mod json; -#[cfg(feature = "platform-value")] +#[cfg(feature = "identity-value-conversion")] mod platform_value; diff --git a/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/platform_value.rs b/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/platform_value.rs index ea2211f690d..28e18bc2f19 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/platform_value.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/v0/conversion/platform_value.rs @@ -27,7 +27,7 @@ impl IdentityPublicKeyPlatformValueConversionMethodsV0 for IdentityPublicKeyV0 { fn from_object( value: Value, _platform_version: &PlatformVersion, - ) -> Result { + ) -> Result { value.try_into().map_err(ProtocolError::ValueError) } } diff --git a/packages/rs-dpp/src/identity/mod.rs b/packages/rs-dpp/src/identity/mod.rs index 917a23637f6..14ddeb8fd2c 100644 --- a/packages/rs-dpp/src/identity/mod.rs +++ b/packages/rs-dpp/src/identity/mod.rs @@ -18,7 +18,7 @@ pub mod errors; pub mod signer; pub mod accessors; -pub(crate) mod conversion; +pub mod conversion; pub mod fields; pub mod identities_contract_keys; #[cfg(feature = "client")] diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs index 6829ea6eaf3..2148f8903a2 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs @@ -6,6 +6,76 @@ use crate::util::hash::hash_double; use crate::{identifier::Identifier, ProtocolError}; use dashcore::OutPoint; +/// Serde module for OutPoint: +/// - Human-readable (JSON): serializes as "txid:vout" string +/// - Non-human-readable (binary/platform_value): serializes as 36 bytes +mod outpoint_serde { + use dashcore::OutPoint; + use serde::de::{Error, Visitor}; + use serde::{Deserialize, Deserializer, Serializer}; + use std::str::FromStr; + + pub fn serialize(out_point: &OutPoint, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + // JSON: serialize as "txid:vout" string + serializer.serialize_str(&out_point.to_string()) + } else { + // Binary: serialize as 36 bytes + let bytes: [u8; 36] = (*out_point).into(); + serializer.serialize_bytes(&bytes) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + // JSON: deserialize from "txid:vout" string + let s = String::deserialize(deserializer)?; + OutPoint::from_str(&s).map_err(|e| D::Error::custom(format!("invalid outpoint: {}", e))) + } else { + // Binary: deserialize from 36 bytes + struct OutPointVisitor; + + impl<'de> Visitor<'de> for OutPointVisitor { + type Value = OutPoint; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("36 bytes for OutPoint") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + if v.len() != 36 { + return Err(E::custom(format!( + "invalid outpoint length: expected 36, got {}", + v.len() + ))); + } + let mut arr = [0u8; 36]; + arr.copy_from_slice(v); + Ok(OutPoint::from(arr)) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + self.visit_bytes(&v) + } + } + + deserializer.deserialize_bytes(OutPointVisitor) + } + } +} + /// Instant Asset Lock Proof is a part of Identity Create and Identity Topup /// transitions. It is a proof that specific output of dash is locked in credits /// pull and the transitions can mint credits and populate identity's balance. @@ -16,6 +86,7 @@ pub struct ChainAssetLockProof { /// Core height on which the asset lock transaction was chain locked or higher pub core_chain_locked_height: u32, /// A reference to Asset Lock Special Transaction ID and output index in the payload + #[serde(with = "outpoint_serde")] pub out_point: OutPoint, } diff --git a/packages/rs-dpp/src/tokens/token_event.rs b/packages/rs-dpp/src/tokens/token_event.rs index b7a852ca430..af6e08b9268 100644 --- a/packages/rs-dpp/src/tokens/token_event.rs +++ b/packages/rs-dpp/src/tokens/token_event.rs @@ -56,6 +56,10 @@ pub type FrozenIdentifier = Identifier; #[derive( Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize, )] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(serde::Serialize, serde::Deserialize) +)] #[platform_serialize(unversioned)] pub enum TokenEvent { /// Event representing the minting of tokens to a recipient. diff --git a/packages/rs-dpp/src/voting/contender_structs/contender/mod.rs b/packages/rs-dpp/src/voting/contender_structs/contender/mod.rs index a3668be5f01..227c23fd1b4 100644 --- a/packages/rs-dpp/src/voting/contender_structs/contender/mod.rs +++ b/packages/rs-dpp/src/voting/contender_structs/contender/mod.rs @@ -31,6 +31,11 @@ pub enum Contender { #[derive( Debug, PartialEq, Eq, Clone, From, Encode, Decode, PlatformSerialize, PlatformDeserialize, )] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] #[platform_serialize(unversioned)] pub enum ContenderWithSerializedDocument { /// V0 diff --git a/packages/rs-dpp/src/voting/contender_structs/contender/v0/mod.rs b/packages/rs-dpp/src/voting/contender_structs/contender/v0/mod.rs index b0d6c682603..86d457a04a5 100644 --- a/packages/rs-dpp/src/voting/contender_structs/contender/v0/mod.rs +++ b/packages/rs-dpp/src/voting/contender_structs/contender/v0/mod.rs @@ -27,6 +27,11 @@ pub struct ContenderV0 { /// This struct holds the identity ID of the contender, the serialized document, /// and the vote tally. #[derive(Debug, PartialEq, Eq, Clone, Default, Encode, Decode)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct ContenderWithSerializedDocumentV0 { /// The identity ID of the contender. pub identity_id: Identifier, diff --git a/packages/rs-dpp/src/voting/vote_info_storage/contested_document_vote_poll_winner_info/mod.rs b/packages/rs-dpp/src/voting/vote_info_storage/contested_document_vote_poll_winner_info/mod.rs index 7e666120149..c1805d199a0 100644 --- a/packages/rs-dpp/src/voting/vote_info_storage/contested_document_vote_poll_winner_info/mod.rs +++ b/packages/rs-dpp/src/voting/vote_info_storage/contested_document_vote_poll_winner_info/mod.rs @@ -1,8 +1,9 @@ use bincode::{Decode, Encode}; use platform_value::Identifier; +use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Encode, Decode)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Encode, Decode, Serialize, Deserialize)] pub enum ContestedDocumentVotePollWinnerInfo { #[default] NoWinner, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs index e9fd69c58d3..ebbdbf1e0d2 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs @@ -179,12 +179,15 @@ mod tests { /// Creates a platform with a Core RPC mock configured to return the given transaction fn create_platform_with_chain_asset_lock_mock( - platform_config: PlatformConfig, + mut platform_config: PlatformConfig, transaction: Transaction, transaction_height: i64, ) -> crate::test::helpers::setup::TempPlatform { let tempdir = TempDir::new().expect("should create temp dir"); + // Set db_path to the tempdir path to avoid trying to write to /var/lib/dash-platform/data + platform_config.db_path = tempdir.path().to_path_buf(); + let mut core_rpc_mock = MockCoreRPCLike::new(); // Set up block hash expectation diff --git a/packages/wasm-dpp2/Cargo.toml b/packages/wasm-dpp2/Cargo.toml index f77791f02ff..3cdade28bdc 100644 --- a/packages/wasm-dpp2/Cargo.toml +++ b/packages/wasm-dpp2/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-dpp2" -version = "1.0.5" +version = { workspace = true } edition = "2024" [lib] @@ -13,17 +13,20 @@ bincode = "=2.0.0-rc.3" wasm-bindgen = { version = "=0.2.103", default-features = false, features = ["serde-serialize"] } dpp = { path = "../rs-dpp", default-features = false, features = [ "state-transition-signing", + "state-transition-value-conversion", "document-json-conversion", "identity-serialization", "identity-value-conversion", + "identity-json-conversion", "data-contract-json-conversion", "document-value-conversion", "bls-signatures", "platform-value", "state-transitions", - "state-transition-serde-conversion" + "state-transition-serde-conversion", + "vote-serde-conversion" ] } -serde-wasm-bindgen = { git = "https://github.com/QuantumExplorer/serde-wasm-bindgen", branch = "feat/not_human_readable" } +serde-wasm-bindgen = { git = "https://github.com/dashpay/serde-wasm-bindgen", branch = "fix/uint8array-to-bytes" } serde = { version = "1.0.197", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } js-sys = "0.3.77" diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index 1ad6027d512..99c2db63f0a 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -41,6 +41,7 @@ }, "devDependencies": { "assert": "^2.0.0", + "bs58": "^4.0.1", "buffer": "^6.0.3", "chai": "^4.3.10", "chai-as-promised": "^7.1.1", diff --git a/packages/wasm-dpp2/src/asset_lock_proof/chain.rs b/packages/wasm-dpp2/src/asset_lock_proof/chain.rs index e46389aaf17..a12b43643f6 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/chain.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/chain.rs @@ -1,20 +1,14 @@ use crate::asset_lock_proof::outpoint::OutPointWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; +use bincode::serde::{decode_from_slice, encode_to_vec}; use dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; -use serde::Deserialize; -use wasm_bindgen::JsValue; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct ChainAssetLockProofParams { - core_chain_locked_height: u32, - out_point: Vec, -} - #[wasm_bindgen(js_name = "ChainAssetLockProof")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct ChainAssetLockProofWasm(ChainAssetLockProof); impl From for ChainAssetLockProof { @@ -52,22 +46,6 @@ impl ChainAssetLockProofWasm { })) } - #[wasm_bindgen(js_name = "fromRawObject")] - pub fn from_raw_value(raw_asset_lock_proof: JsValue) -> WasmDppResult { - let parameters: ChainAssetLockProofParams = - serde_wasm_bindgen::from_value(raw_asset_lock_proof) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - - let out_point: [u8; 36] = parameters - .out_point - .try_into() - .map_err(|_| WasmDppError::invalid_argument("outPoint must be a 36 byte array"))?; - - let rs_proof = ChainAssetLockProof::new(parameters.core_chain_locked_height, out_point); - - Ok(ChainAssetLockProofWasm(rs_proof)) - } - #[wasm_bindgen(setter = "coreChainLockedHeight")] pub fn set_core_chain_locked_height(&mut self, core_chain_locked_height: u32) { self.0.core_chain_locked_height = core_chain_locked_height; @@ -79,12 +57,12 @@ impl ChainAssetLockProofWasm { } #[wasm_bindgen(getter = "coreChainLockedHeight")] - pub fn get_core_chain_locked_height(self) -> u32 { + pub fn get_core_chain_locked_height(&self) -> u32 { self.0.core_chain_locked_height } #[wasm_bindgen(getter = "outPoint")] - pub fn get_out_point(self) -> OutPointWasm { + pub fn get_out_point(&self) -> OutPointWasm { self.0.out_point.into() } @@ -94,4 +72,20 @@ impl ChainAssetLockProofWasm { identifier.into() } + + #[wasm_bindgen(js_name = "toBytes")] + pub fn to_bytes(&self) -> WasmDppResult> { + encode_to_vec(&self.0, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = "fromBytes")] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let proof: ChainAssetLockProof = decode_from_slice(&bytes, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string()))? + .0; + Ok(ChainAssetLockProofWasm(proof)) + } } + +impl_wasm_conversions!(ChainAssetLockProofWasm, ChainAssetLockProof); diff --git a/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_asset_lock_proof.rs b/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_asset_lock_proof.rs index 2ca5f4fe8b2..11d6eaf7549 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_asset_lock_proof.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_asset_lock_proof.rs @@ -1,23 +1,12 @@ -use crate::asset_lock_proof::instant::instant_lock::InstantLockWasm; use crate::asset_lock_proof::outpoint::OutPointWasm; -use crate::asset_lock_proof::tx_out::TxOutWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use dpp::dashcore::consensus::{deserialize, serialize}; use dpp::dashcore::{InstantLock, Transaction}; use dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct InstantAssetLockProofRAW { - instant_lock: Vec, - transaction: Vec, - output_index: u32, -} - #[derive(Clone)] #[wasm_bindgen(js_name = "InstantAssetLockProof")] pub struct InstantAssetLockProofWasm(InstantAssetLockProof); @@ -64,31 +53,9 @@ impl InstantAssetLockProofWasm { })) } - #[wasm_bindgen(js_name = "fromObject")] - pub fn from_object(value: JsValue) -> WasmDppResult { - let parameters: InstantAssetLockProofRAW = serde_wasm_bindgen::from_value(value) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - - InstantAssetLockProofWasm::new( - parameters.instant_lock, - parameters.transaction, - parameters.output_index, - ) - } - - #[wasm_bindgen(js_name = "toObject")] - pub fn to_object(&self) -> WasmDppResult { - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - - self.0 - .to_object()? - .serialize(&serializer) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = "getOutput")] - pub fn get_output(&self) -> Option { - self.0.output().map(|output| output.clone().into()) + pub fn get_output(&self) -> Option> { + self.0.output().map(serialize) } #[wasm_bindgen(js_name = "getOutPoint")] @@ -102,8 +69,8 @@ impl InstantAssetLockProofWasm { } #[wasm_bindgen(getter = "instantLock")] - pub fn get_instant_lock(&self) -> InstantLockWasm { - self.0.instant_lock.clone().into() + pub fn get_instant_lock(&self) -> Vec { + serialize(&self.0.instant_lock) } #[wasm_bindgen(setter = "outputIndex")] @@ -112,8 +79,10 @@ impl InstantAssetLockProofWasm { } #[wasm_bindgen(setter = "instantLock")] - pub fn set_instant_lock(&mut self, instant_lock: &InstantLockWasm) { - self.0.instant_lock = instant_lock.clone().into(); + pub fn set_instant_lock(&mut self, instant_lock: Vec) -> WasmDppResult<()> { + self.0.instant_lock = deserialize(instant_lock.as_slice()) + .map_err(|err| WasmDppError::serialization(err.to_string()))?; + Ok(()) } #[wasm_bindgen(js_name=getTransaction)] @@ -122,12 +91,6 @@ impl InstantAssetLockProofWasm { serialize(transaction) } - #[wasm_bindgen(js_name=getInstantLockBytes)] - pub fn get_instant_lock_bytes(&self) -> Vec { - let instant_lock = self.0.instant_lock(); - serialize(instant_lock) - } - #[wasm_bindgen(js_name = "createIdentityId")] pub fn create_identifier(&self) -> WasmDppResult { let identifier = self.0.create_identifier()?; @@ -135,3 +98,5 @@ impl InstantAssetLockProofWasm { Ok(identifier.into()) } } + +impl_wasm_conversions!(InstantAssetLockProofWasm, InstantAssetLockProof); diff --git a/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_lock.rs b/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_lock.rs deleted file mode 100644 index 1d25633b32d..00000000000 --- a/packages/wasm-dpp2/src/asset_lock_proof/instant/instant_lock.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::asset_lock_proof::outpoint::OutPointWasm; -use crate::error::{WasmDppError, WasmDppResult}; -use dpp::dashcore::bls_sig_utils::BLSSignature; -use dpp::dashcore::hash_types::CycleHash; -use dpp::dashcore::hashes::hex::FromHex; -use dpp::dashcore::secp256k1::hashes::hex::Case::Lower; -use dpp::dashcore::secp256k1::hashes::hex::DisplayHex; -use dpp::dashcore::{InstantLock, Txid}; -use std::str::FromStr; -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(js_name = "InstantLock")] -#[derive(Clone)] -pub struct InstantLockWasm(InstantLock); - -impl From for InstantLock { - fn from(value: InstantLockWasm) -> Self { - value.0 - } -} - -impl From for InstantLockWasm { - fn from(value: InstantLock) -> Self { - InstantLockWasm(value) - } -} - -#[wasm_bindgen(js_class = InstantLock)] -impl InstantLockWasm { - #[wasm_bindgen(getter = __type)] - pub fn type_name(&self) -> String { - "InstantLock".to_string() - } - - #[wasm_bindgen(getter = __struct)] - pub fn struct_name() -> String { - "InstantLock".to_string() - } - - #[wasm_bindgen(constructor)] - pub fn new( - version: u8, - js_inputs: &js_sys::Array, - txid: String, - cycle_hash: String, - bls_signature: String, - ) -> WasmDppResult { - let inputs = OutPointWasm::vec_from_js_value(js_inputs)?; - - Ok(InstantLockWasm(InstantLock { - version, - inputs: inputs.iter().map(|input| input.clone().into()).collect(), - txid: Txid::from_hex(&txid) - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - cyclehash: CycleHash::from_str(&cycle_hash) - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - signature: BLSSignature::from_hex(&bls_signature) - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - })) - } - - #[wasm_bindgen(getter = "version")] - pub fn get_version(&self) -> u8 { - self.0.version - } - - #[wasm_bindgen(getter = "inputs")] - pub fn get_inputs(&self) -> Vec { - self.0.inputs.iter().map(|input| (*input).into()).collect() - } - - #[wasm_bindgen(getter = "txid")] - pub fn get_txid(&self) -> String { - self.0.txid.to_hex() - } - - #[wasm_bindgen(getter = "cyclehash")] - pub fn get_cycle_hash(&self) -> String { - self.0.cyclehash.to_string() - } - - #[wasm_bindgen(getter = "blsSignature")] - pub fn get_bls_signature(&self) -> String { - self.0.signature.to_bytes().to_hex_string(Lower) - } - - #[wasm_bindgen(setter = "version")] - pub fn set_version(&mut self, v: u8) { - self.0.version = v; - } - - #[wasm_bindgen(setter = "inputs")] - pub fn set_inputs(&mut self, inputs: &js_sys::Array) -> WasmDppResult<()> { - let inputs = OutPointWasm::vec_from_js_value(inputs)?; - self.0.inputs = inputs.iter().map(|input| input.clone().into()).collect(); - Ok(()) - } - - #[wasm_bindgen(setter = "txid")] - pub fn set_txid(&mut self, txid: String) -> WasmDppResult<()> { - self.0.txid = - Txid::from_hex(&txid).map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(()) - } - - #[wasm_bindgen(setter = "cyclehash")] - pub fn set_cycle_hash(&mut self, cycle_hash: String) -> WasmDppResult<()> { - self.0.cyclehash = CycleHash::from_str(&cycle_hash) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(()) - } - - #[wasm_bindgen(setter = "blsSignature")] - pub fn set_bls_signature(&mut self, bls_signature: String) -> WasmDppResult<()> { - self.0.signature = BLSSignature::from_hex(&bls_signature) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(()) - } -} diff --git a/packages/wasm-dpp2/src/asset_lock_proof/instant/mod.rs b/packages/wasm-dpp2/src/asset_lock_proof/instant/mod.rs index a4eeeddb6ed..8dcd8fcbd4a 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/instant/mod.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/instant/mod.rs @@ -1,4 +1,3 @@ mod instant_asset_lock_proof; -mod instant_lock; pub use instant_asset_lock_proof::InstantAssetLockProofWasm; diff --git a/packages/wasm-dpp2/src/asset_lock_proof/mod.rs b/packages/wasm-dpp2/src/asset_lock_proof/mod.rs index 3bff8ec26b7..2fdf93edcbd 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/mod.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/mod.rs @@ -2,6 +2,5 @@ pub mod chain; pub mod instant; pub mod outpoint; mod proof; -mod tx_out; pub use proof::AssetLockProofWasm; diff --git a/packages/wasm-dpp2/src/asset_lock_proof/outpoint.rs b/packages/wasm-dpp2/src/asset_lock_proof/outpoint.rs index 77f3621d6f5..0449bc555e2 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/outpoint.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/outpoint.rs @@ -77,7 +77,7 @@ impl OutPointWasm { encode(slice.as_slice(), Hex) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> String { let slice: [u8; 36] = self.0.into(); diff --git a/packages/wasm-dpp2/src/asset_lock_proof/proof.rs b/packages/wasm-dpp2/src/asset_lock_proof/proof.rs index 2dcd377e73a..c0a7f8520d9 100644 --- a/packages/wasm-dpp2/src/asset_lock_proof/proof.rs +++ b/packages/wasm-dpp2/src/asset_lock_proof/proof.rs @@ -5,9 +5,9 @@ use crate::asset_lock_proof::outpoint::OutPointWasm; use crate::enums::lock_types::AssetLockProofTypeWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::utils::{IntoWasm, get_class_type}; +use crate::utils::{IntoWasm, JsValueExt, get_class_type}; use dpp::prelude::AssetLockProof; -use serde::Serialize; +use js_sys::{Object, Reflect}; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -141,34 +141,131 @@ impl AssetLockProofWasm { #[wasm_bindgen(js_name = "toObject")] pub fn to_object(&self) -> WasmDppResult { - let json_value = self.0.to_raw_object()?; + let inner_object = match &self.0 { + AssetLockProof::Chain(chain) => { + ChainAssetLockProofWasm::from(chain.clone()).to_object()? + } + AssetLockProof::Instant(instant) => { + InstantAssetLockProofWasm::from(instant.clone()).to_object()? + } + }; - json_value - .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|e| WasmDppError::serialization(e.to_string())) + // Add type field: 0 = Instant, 1 = Chain + let object = Object::from(inner_object); + let proof_type: u8 = match &self.0 { + AssetLockProof::Instant(_) => 0, + AssetLockProof::Chain(_) => 1, + }; + Reflect::set( + &object, + &JsValue::from_str("type"), + &JsValue::from(proof_type), + ) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + Ok(object.into()) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + let object = Object::from(js_value.clone()); + let proof_type = Reflect::get(&object, &JsValue::from_str("type")) + .map_err(|e| WasmDppError::invalid_argument(e.error_message()))?; + + let type_num = proof_type.as_f64().ok_or_else(|| { + WasmDppError::invalid_argument( + "AssetLockProof object must have a 'type' field (0 = Instant, 1 = Chain)" + .to_string(), + ) + })?; + + match type_num as u8 { + 0 => InstantAssetLockProofWasm::from_object(js_value).map(AssetLockProofWasm::from), + 1 => ChainAssetLockProofWasm::from_object(js_value).map(AssetLockProofWasm::from), + _ => Err(WasmDppError::invalid_argument(format!( + "Unknown AssetLockProof type: {}", + type_num + ))), + } + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> WasmDppResult { + let inner_json = match &self.0 { + AssetLockProof::Chain(chain) => { + ChainAssetLockProofWasm::from(chain.clone()).to_json()? + } + AssetLockProof::Instant(instant) => { + InstantAssetLockProofWasm::from(instant.clone()).to_json()? + } + }; + + // Add type field: 0 = Instant, 1 = Chain + let object = Object::from(inner_json); + let proof_type: u8 = match &self.0 { + AssetLockProof::Instant(_) => 0, + AssetLockProof::Chain(_) => 1, + }; + Reflect::set( + &object, + &JsValue::from_str("type"), + &JsValue::from(proof_type), + ) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + Ok(object.into()) + } + + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + let object = Object::from(js_value.clone()); + let proof_type = Reflect::get(&object, &JsValue::from_str("type")) + .map_err(|e| WasmDppError::invalid_argument(e.error_message()))?; + + let type_num = proof_type.as_f64().ok_or_else(|| { + WasmDppError::invalid_argument( + "AssetLockProof JSON must have a 'type' field (0 = Instant, 1 = Chain)".to_string(), + ) + })?; + + match type_num as u8 { + 0 => InstantAssetLockProofWasm::from_json(js_value).map(AssetLockProofWasm::from), + 1 => ChainAssetLockProofWasm::from_json(js_value).map(AssetLockProofWasm::from), + _ => Err(WasmDppError::invalid_argument(format!( + "Unknown AssetLockProof type: {}", + type_num + ))), + } } #[wasm_bindgen(js_name = "toHex")] - pub fn to_string(&self) -> WasmDppResult { - let json = serde_json::to_string(&self.0) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(hex::encode(json)) + pub fn to_hex(&self) -> WasmDppResult { + let bytes = bincode::encode_to_vec(&self.0, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(hex::encode(bytes)) } #[wasm_bindgen(js_name = "fromHex")] pub fn from_hex(asset_lock_proof: String) -> WasmDppResult { - let asset_lock_proof_bytes = hex::decode(&asset_lock_proof).map_err(|e| { - WasmDppError::serialization(format!("Invalid asset lock proof hex: {}", e)) - })?; - - let json_str = String::from_utf8(asset_lock_proof_bytes).map_err(|e| { - WasmDppError::serialization(format!("Invalid UTF-8 in asset lock proof: {}", e)) - })?; + let bytes = hex::decode(asset_lock_proof) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + let proof: AssetLockProof = bincode::decode_from_slice(&bytes, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string()))? + .0; + Ok(AssetLockProofWasm(proof)) + } - let asset_lock_proof: AssetLockProof = serde_json::from_str(&json_str).map_err(|e| { - WasmDppError::serialization(format!("Failed to parse asset lock proof JSON: {}", e)) - })?; + #[wasm_bindgen(js_name = "toBytes")] + pub fn to_bytes(&self) -> WasmDppResult> { + bincode::encode_to_vec(&self.0, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } - Ok(AssetLockProofWasm(asset_lock_proof)) + #[wasm_bindgen(js_name = "fromBytes")] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let proof: AssetLockProof = bincode::decode_from_slice(&bytes, bincode::config::standard()) + .map_err(|e| WasmDppError::serialization(e.to_string()))? + .0; + Ok(AssetLockProofWasm(proof)) } } diff --git a/packages/wasm-dpp2/src/asset_lock_proof/tx_out.rs b/packages/wasm-dpp2/src/asset_lock_proof/tx_out.rs deleted file mode 100644 index 51df4ca0ae2..00000000000 --- a/packages/wasm-dpp2/src/asset_lock_proof/tx_out.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::error::{WasmDppError, WasmDppResult}; -use dpp::dashcore::{ScriptBuf, TxOut}; -use js_sys::Uint8Array; -use wasm_bindgen::JsValue; -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(js_name = "TxOut")] -#[derive(Clone)] -pub struct TxOutWasm(TxOut); - -impl From for TxOutWasm { - fn from(value: TxOut) -> Self { - TxOutWasm(value) - } -} - -impl From for TxOut { - fn from(value: TxOutWasm) -> Self { - value.0 - } -} - -#[wasm_bindgen(js_class = TxOut)] -impl TxOutWasm { - #[wasm_bindgen(getter = __type)] - pub fn type_name(&self) -> String { - "TxOut".to_string() - } - - #[wasm_bindgen(getter = __struct)] - pub fn struct_name() -> String { - "TxOut".to_string() - } - - #[wasm_bindgen(constructor)] - pub fn new(value: u64, script_pubkey: JsValue) -> WasmDppResult { - let tx_out: WasmDppResult = match script_pubkey.is_array() { - true => Ok(TxOut { - value, - script_pubkey: ScriptBuf::from_bytes(Uint8Array::from(script_pubkey).to_vec()), - }), - false => match script_pubkey.is_string() { - true => { - let hex = script_pubkey.as_string().ok_or_else(|| { - WasmDppError::invalid_argument("Script pubkey must be string") - })?; - - let script = ScriptBuf::from_hex(&hex) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - - Ok(TxOut { - value, - script_pubkey: script, - }) - } - false => Err(WasmDppError::invalid_argument("Invalid script pubkey")), - }, - }; - - Ok(TxOutWasm(tx_out?)) - } - - #[wasm_bindgen(getter = "value")] - pub fn get_value(&self) -> u64 { - self.0.value - } - - #[wasm_bindgen(getter = "scriptPubKeyHex")] - pub fn get_script_pubkey_hex(&self) -> String { - self.0.script_pubkey.to_hex_string() - } - - #[wasm_bindgen(getter = "scriptPubKeyBytes")] - pub fn get_script_pubkey_bytes(&self) -> Vec { - self.0.script_pubkey.to_bytes() - } - - #[wasm_bindgen(setter = "value")] - pub fn set_value(&mut self, value: u64) { - self.0.value = value; - } - - #[wasm_bindgen(setter = "scriptPubKeyHex")] - pub fn set_script_pubkey_hex(&mut self, script_pubkey_hex: String) -> WasmDppResult<()> { - self.0.script_pubkey = ScriptBuf::from_hex(&script_pubkey_hex) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(()) - } - - #[wasm_bindgen(setter = "scriptPubKeyBytes")] - pub fn set_script_pubkey_bytes(&mut self, script_pubkey_bytes: Vec) { - self.0.script_pubkey = ScriptBuf::from_bytes(script_pubkey_bytes); - } - - #[wasm_bindgen(js_name = "getScriptPubKeyASM")] - pub fn get_script_pubkey_asm(&self) -> String { - self.0.script_pubkey.to_asm_string() - } -} diff --git a/packages/wasm-dpp2/src/block.rs b/packages/wasm-dpp2/src/block.rs index 576ee7fac33..fde4fbd7468 100644 --- a/packages/wasm-dpp2/src/block.rs +++ b/packages/wasm-dpp2/src/block.rs @@ -1,45 +1,70 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::impl_wasm_conversions; use dpp::block::block_info::BlockInfo; -use wasm_bindgen::prelude::wasm_bindgen; +use dpp::block::epoch::Epoch; +use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = "BlockInfo")] #[derive(Clone)] -pub struct BlockInfoWasm { - time_ms: u64, - height: u64, - core_height: u64, - epoch_index: u16, -} +pub struct BlockInfoWasm(BlockInfo); #[wasm_bindgen(js_class = BlockInfo)] impl BlockInfoWasm { + #[wasm_bindgen(constructor)] + pub fn new( + time_ms: u64, + height: u64, + core_height: u32, + epoch_index: u16, + ) -> WasmDppResult { + let epoch = + Epoch::new(epoch_index).map_err(|e| WasmDppError::invalid_argument(e.to_string()))?; + + Ok(BlockInfoWasm(BlockInfo { + time_ms, + height, + core_height, + epoch, + })) + } + #[wasm_bindgen(getter = timeMs)] pub fn time_ms(&self) -> u64 { - self.time_ms + self.0.time_ms } #[wasm_bindgen(getter)] pub fn height(&self) -> u64 { - self.height + self.0.height } #[wasm_bindgen(getter = coreHeight)] - pub fn core_height(&self) -> u64 { - self.core_height + pub fn core_height(&self) -> u32 { + self.0.core_height } #[wasm_bindgen(getter = epochIndex)] pub fn epoch_index(&self) -> u16 { - self.epoch_index + self.0.epoch.index } } impl From for BlockInfoWasm { fn from(block: BlockInfo) -> Self { - Self { - time_ms: block.time_ms, - height: block.height, - core_height: block.core_height as u64, - epoch_index: block.epoch.index, - } + BlockInfoWasm(block) } } + +impl From for BlockInfo { + fn from(wasm: BlockInfoWasm) -> Self { + wasm.0 + } +} + +impl From<&BlockInfoWasm> for BlockInfo { + fn from(wasm: &BlockInfoWasm) -> Self { + wasm.0 + } +} + +impl_wasm_conversions!(BlockInfoWasm, BlockInfo); diff --git a/packages/wasm-dpp2/src/core_script.rs b/packages/wasm-dpp2/src/core_script.rs index 0df47fca743..33778671314 100644 --- a/packages/wasm-dpp2/src/core_script.rs +++ b/packages/wasm-dpp2/src/core_script.rs @@ -42,39 +42,42 @@ impl CoreScriptWasm { } #[wasm_bindgen(js_name = "newP2PKH")] - pub fn new_p2pkh(js_key_hash: Vec) -> Self { - let mut key_hash = [0u8; 20]; - let bytes = js_key_hash.as_slice(); - let len = bytes.len().min(32); - key_hash[..len].copy_from_slice(&bytes[..len]); + pub fn new_p2pkh(key_hash: Vec) -> Self { + let mut key_hash_bytes = [0u8; 20]; + let bytes = key_hash.as_slice(); + let len = bytes.len().min(key_hash_bytes.len()); + key_hash_bytes[..len].copy_from_slice(&bytes[..len]); - CoreScriptWasm(CoreScript::new_p2pkh(key_hash)) + CoreScriptWasm(CoreScript::new_p2pkh(key_hash_bytes)) } #[wasm_bindgen(js_name = "newP2SH")] - pub fn new_p2sh(js_script_hash: Vec) -> Self { - let mut script_hash = [0u8; 20]; - let bytes = js_script_hash.as_slice(); - let len = bytes.len().min(32); - script_hash[..len].copy_from_slice(&bytes[..len]); + pub fn new_p2sh(script_hash: Vec) -> Self { + let mut script_hash_bytes = [0u8; 20]; + let bytes = script_hash.as_slice(); + let len = bytes.len().min(script_hash_bytes.len()); + script_hash_bytes[..len].copy_from_slice(&bytes[..len]); let mut bytes = vec![ opcodes::all::OP_HASH160.to_u8(), opcodes::all::OP_PUSHBYTES_20.to_u8(), ]; - bytes.extend_from_slice(&script_hash); + bytes.extend_from_slice(&script_hash_bytes); bytes.push(opcodes::all::OP_EQUAL.to_u8()); Self::from_bytes(bytes) } #[wasm_bindgen(js_name = "toAddress")] - pub fn to_address(&self, js_network: &JsValue) -> WasmDppResult { - let network = NetworkWasm::try_from(js_network.clone())?; + pub fn to_address( + &self, + #[wasm_bindgen(unchecked_param_type = "Network | string")] network: &JsValue, + ) -> WasmDppResult { + let network_wasm = NetworkWasm::try_from(network.clone())?; let payload = Payload::from_script(self.0.as_script()) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; - let address = Address::new(network.into(), payload); + let address = Address::new(network_wasm.into(), payload); Ok(address.to_string()) } @@ -94,12 +97,12 @@ impl CoreScriptWasm { encode(self.0.to_bytes().as_slice(), Hex) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> String { encode(self.0.to_bytes().as_slice(), Base64) } - #[wasm_bindgen(js_name = "ASMString")] + #[wasm_bindgen(js_name = "toASMString")] pub fn to_asm_string(&self) -> String { self.0.to_asm_string() } diff --git a/packages/wasm-dpp2/src/data_contract/contract_bounds.rs b/packages/wasm-dpp2/src/data_contract/contract_bounds.rs index eda7594d723..1242c9fafbd 100644 --- a/packages/wasm-dpp2/src/data_contract/contract_bounds.rs +++ b/packages/wasm-dpp2/src/data_contract/contract_bounds.rs @@ -1,5 +1,6 @@ use crate::error::WasmDppResult; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use dpp::identity::contract_bounds::ContractBounds; use dpp::prelude::Identifier; use wasm_bindgen::JsValue; @@ -134,3 +135,5 @@ impl ContractBoundsWasm { } } } + +impl_wasm_conversions!(ContractBoundsWasm, ContractBounds); diff --git a/packages/wasm-dpp2/src/data_contract/document/methods.rs b/packages/wasm-dpp2/src/data_contract/document/methods.rs deleted file mode 100644 index b754c49edeb..00000000000 --- a/packages/wasm-dpp2/src/data_contract/document/methods.rs +++ /dev/null @@ -1,470 +0,0 @@ -use crate::data_contract::DataContractWasm; -use crate::data_contract::document::DocumentWasm; -use crate::enums::platform::PlatformVersionWasm; -use crate::error::{WasmDppError, WasmDppResult}; -use crate::identifier::IdentifierWasm; -use crate::utils::ToSerdeJSONExt; -use dpp::dashcore::hashes::serde::Serialize; -use dpp::data_contract::JsonValue; -use dpp::document::Document; -use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0; -use dpp::identifier::Identifier; -use dpp::platform_value::Value; -use dpp::platform_value::converter::serde_json::BTreeValueJsonConverter; -use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; -use dpp::platform_value::string_encoding::{decode, encode}; -use dpp::prelude::Revision; -use dpp::util::entropy_generator; -use dpp::util::entropy_generator::EntropyGenerator; -use std::collections::BTreeMap; -use wasm_bindgen::JsValue; -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(js_class = Document)] -impl DocumentWasm { - #[wasm_bindgen(getter = __type)] - pub fn type_name(&self) -> String { - "Document".to_string() - } - - #[wasm_bindgen(getter = __struct)] - pub fn struct_name() -> String { - "Document".to_string() - } - - #[wasm_bindgen(constructor)] - pub fn new( - js_raw_document: JsValue, - js_document_type_name: &str, - js_revision: u64, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_data_contract_id: &JsValue, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_owner_id: &JsValue, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_document_id: &JsValue, - ) -> WasmDppResult { - let data_contract_id: Identifier = IdentifierWasm::try_from(js_data_contract_id)?.into(); - let owner_id: Identifier = IdentifierWasm::try_from(js_owner_id)?.into(); - - let revision = Revision::from(js_revision); - - let document = js_raw_document.with_serde_to_platform_value_map()?; - - let revision = Revision::from(revision); - - let entropy = entropy_generator::DefaultEntropyGenerator - .generate() - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - - let document_id: Identifier = match js_document_id.is_undefined() { - true => crate::utils::generate_document_id_v0( - &data_contract_id, - &owner_id, - js_document_type_name, - &entropy, - )?, - false => IdentifierWasm::try_from(js_document_id)?.into(), - }; - - Ok(DocumentWasm { - owner_id: owner_id.into(), - entropy: Some(entropy), - id: document_id.into(), - document_type_name: js_document_type_name.to_string(), - data_contract_id: data_contract_id.into(), - properties: document, - revision: Some(revision), - created_at: None, - updated_at: None, - transferred_at: None, - created_at_block_height: None, - updated_at_block_height: None, - transferred_at_block_height: None, - created_at_core_block_height: None, - updated_at_core_block_height: None, - transferred_at_core_block_height: None, - creator_id: None, - }) - } - - #[wasm_bindgen(getter=id)] - pub fn get_id(&self) -> IdentifierWasm { - self.id - } - - #[wasm_bindgen(getter=entropy)] - pub fn get_entropy(&self) -> Option> { - self.entropy.map(|entropy| entropy.to_vec()) - } - - #[wasm_bindgen(getter=dataContractId)] - pub fn get_data_contract_id(&self) -> IdentifierWasm { - self.data_contract_id - } - - #[wasm_bindgen(getter=ownerId)] - pub fn get_owner_id(&self) -> IdentifierWasm { - self.owner_id - } - - #[wasm_bindgen(getter=properties)] - pub fn get_properties(&self) -> WasmDppResult { - let json_value: JsonValue = self - .properties - .clone() - .to_json_value() - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - - let js_value = json_value - .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; - Ok(js_value) - } - - #[wasm_bindgen(getter=revision)] - pub fn get_revision(&self) -> Option { - self.revision - } - - #[wasm_bindgen(getter=createdAt)] - pub fn get_created_at(&self) -> Option { - self.created_at - } - - #[wasm_bindgen(getter=updatedAt)] - pub fn get_updated_at(&self) -> Option { - self.updated_at - } - - #[wasm_bindgen(getter=transferredAt)] - pub fn get_transferred_at(&self) -> Option { - self.transferred_at - } - - #[wasm_bindgen(getter=createdAtBlockHeight)] - pub fn get_created_at_block_height(&self) -> Option { - self.created_at_block_height - } - - #[wasm_bindgen(getter=updatedAtBlockHeight)] - pub fn get_updated_at_block_height(&self) -> Option { - self.updated_at_block_height - } - - #[wasm_bindgen(getter=transferredAtBlockHeight)] - pub fn get_transferred_at_block_height(&self) -> Option { - self.transferred_at_block_height - } - - #[wasm_bindgen(getter=createdAtCoreBlockHeight)] - pub fn get_created_at_core_block_height(&self) -> Option { - self.created_at_core_block_height - } - - #[wasm_bindgen(getter=updatedAtCoreBlockHeight)] - pub fn get_updated_at_core_block_height(&self) -> Option { - self.updated_at_core_block_height - } - - #[wasm_bindgen(getter=transferredAtCoreBlockHeight)] - pub fn get_transferred_at_core_block_height(&self) -> Option { - self.transferred_at_core_block_height - } - - #[wasm_bindgen(getter=documentTypeName)] - pub fn get_document_type_name(&self) -> String { - self.document_type_name.clone() - } - - #[wasm_bindgen(setter=id)] - pub fn set_id( - &mut self, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] id: &JsValue, - ) -> WasmDppResult<()> { - self.id = IdentifierWasm::try_from(id)?; - Ok(()) - } - - #[wasm_bindgen(setter=entropy)] - pub fn set_entropy(&mut self, entropy: JsValue) -> WasmDppResult<()> { - if entropy.is_undefined() { - self.entropy = None; - return Ok(()); - } - - let value = entropy.with_serde_to_platform_value()?; - - let bytes = value.as_bytes().ok_or_else(|| { - WasmDppError::invalid_argument("Entropy must be provided as a byte array") - })?; - - let mut entropy_bytes = [0u8; 32]; - let len = bytes.len().min(32); - entropy_bytes[..len].copy_from_slice(&bytes[..len]); - self.entropy = Some(entropy_bytes); - - Ok(()) - } - - #[wasm_bindgen(setter=dataContractId)] - pub fn set_js_data_contract_id( - &mut self, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_contract_id: &JsValue, - ) -> WasmDppResult<()> { - self.data_contract_id = IdentifierWasm::try_from(js_contract_id)?; - - Ok(()) - } - - #[wasm_bindgen(setter=ownerId)] - pub fn set_owner_id( - &mut self, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] id: &JsValue, - ) -> WasmDppResult<()> { - self.owner_id = IdentifierWasm::try_from(id)?; - Ok(()) - } - - #[wasm_bindgen(setter=properties)] - pub fn set_properties(&mut self, properties: JsValue) -> WasmDppResult<()> { - self.properties = properties.with_serde_to_platform_value_map()?; - Ok(()) - } - - #[wasm_bindgen(setter=revision)] - pub fn set_revision(&mut self, revision: Option) { - self.revision = revision - } - - #[wasm_bindgen(setter=createdAt)] - pub fn set_created_at(&mut self, created_at: Option) { - self.created_at = created_at - } - - #[wasm_bindgen(setter=updatedAt)] - pub fn set_get_updated_at(&mut self, updated_at: Option) { - self.updated_at = updated_at - } - - #[wasm_bindgen(setter=transferredAt)] - pub fn set_transferred_at(&mut self, transferred_at: Option) { - self.transferred_at = transferred_at - } - - #[wasm_bindgen(setter=createdAtBlockHeight)] - pub fn set_created_at_block_height(&mut self, created_at_block_height: Option) { - self.created_at_block_height = created_at_block_height - } - - #[wasm_bindgen(setter=updatedAtBlockHeight)] - pub fn set_updated_at_block_height(&mut self, updated_at_block_height: Option) { - self.updated_at_block_height = updated_at_block_height - } - - #[wasm_bindgen(setter=transferredAtBlockHeight)] - pub fn set_transferred_at_block_height(&mut self, transferred_at_block_height: Option) { - self.transferred_at_block_height = transferred_at_block_height - } - - #[wasm_bindgen(setter=createdAtCoreBlockHeight)] - pub fn set_created_at_core_block_height(&mut self, created_at_core_block_height: Option) { - self.created_at_core_block_height = created_at_core_block_height - } - - #[wasm_bindgen(setter=updatedAtCoreBlockHeight)] - pub fn set_updated_at_core_block_height(&mut self, updated_at_core_block_height: Option) { - self.updated_at_core_block_height = updated_at_core_block_height - } - - #[wasm_bindgen(setter=transferredAtCoreBlockHeight)] - pub fn set_transferred_at_core_block_height( - &mut self, - transferred_at_core_block_height: Option, - ) { - self.transferred_at_core_block_height = transferred_at_core_block_height - } - - #[wasm_bindgen(setter=documentTypeName)] - pub fn set_document_type_name(&mut self, document_type_name: &str) { - self.document_type_name = document_type_name.to_string(); - } - - #[wasm_bindgen(js_name=bytes)] - pub fn to_bytes( - &self, - data_contract: &DataContractWasm, - js_platform_version: JsValue, - ) -> WasmDppResult> { - let platform_version = match js_platform_version.is_undefined() { - true => PlatformVersionWasm::default(), - false => PlatformVersionWasm::try_from(js_platform_version)?, - }; - - let rs_document: Document = Document::from(self.clone()); - - let document_type_ref = data_contract - .get_document_type_ref_by_name(self.get_document_type_name()) - .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; - - DocumentPlatformConversionMethodsV0::serialize( - &rs_document, - document_type_ref, - &data_contract.clone().into(), - &platform_version.into(), - ) - .map_err(Into::into) - } - - #[wasm_bindgen(js_name=hex)] - pub fn to_hex( - &self, - data_contract: &DataContractWasm, - js_platform_version: JsValue, - ) -> WasmDppResult { - Ok(encode( - self.to_bytes(data_contract, js_platform_version)? - .as_slice(), - Hex, - )) - } - - #[wasm_bindgen(js_name=base64)] - pub fn to_base64( - &self, - data_contract: &DataContractWasm, - js_platform_version: JsValue, - ) -> WasmDppResult { - Ok(encode( - self.to_bytes(data_contract, js_platform_version)? - .as_slice(), - Base64, - )) - } - - #[wasm_bindgen(js_name=fromBytes)] - pub fn from_bytes( - bytes: Vec, - data_contract: &DataContractWasm, - type_name: String, - js_platform_version: JsValue, - ) -> WasmDppResult { - let platform_version = match js_platform_version.is_undefined() { - true => PlatformVersionWasm::default(), - false => PlatformVersionWasm::try_from(js_platform_version)?, - }; - - let document_type_ref = match data_contract.get_document_type_ref_by_name(type_name.clone()) - { - Ok(type_ref) => Ok(type_ref), - Err(err) => Err(WasmDppError::invalid_argument(err.to_string())), - }?; - - let rs_document = Document::from_bytes( - bytes.as_slice(), - document_type_ref, - &platform_version.into(), - )?; - - let mut js_document = DocumentWasm::from(rs_document); - - js_document.set_document_type_name(type_name.clone().as_str()); - js_document.set_data_contract_id(&data_contract.get_id()); - - Ok(js_document) - } - - #[wasm_bindgen(js_name=fromHex)] - pub fn from_hex( - hex: String, - data_contract: &DataContractWasm, - type_name: String, - js_platform_version: JsValue, - ) -> WasmDppResult { - DocumentWasm::from_bytes( - decode(hex.as_str(), Hex) - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - data_contract, - type_name, - js_platform_version, - ) - } - - #[wasm_bindgen(js_name=fromBase64)] - pub fn from_base64( - base64: String, - data_contract: &DataContractWasm, - type_name: String, - js_platform_version: JsValue, - ) -> WasmDppResult { - DocumentWasm::from_bytes( - decode(base64.as_str(), Base64) - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - data_contract, - type_name, - js_platform_version, - ) - } - - #[wasm_bindgen(js_name=generateId)] - pub fn generate_id( - js_document_type_name: &str, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_owner_id: &JsValue, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_data_contract_id: &JsValue, - opt_entropy: Option>, - ) -> WasmDppResult> { - let owner_id: Identifier = IdentifierWasm::try_from(js_owner_id)?.into(); - let data_contract_id: Identifier = IdentifierWasm::try_from(js_data_contract_id)?.into(); - - let entropy: [u8; 32] = match opt_entropy { - Some(entropy_vec) => { - let mut entropy = [0u8; 32]; - let bytes = entropy_vec.as_slice(); - let len = bytes.len().min(32); - entropy[..len].copy_from_slice(&bytes[..len]); - entropy - } - None => entropy_generator::DefaultEntropyGenerator - .generate() - .map_err(|err| WasmDppError::serialization(err.to_string()))?, - }; - - let identifier = crate::utils::generate_document_id_v0( - &data_contract_id, - &owner_id, - js_document_type_name, - &entropy, - )?; - - Ok(identifier.to_vec()) - } -} - -impl DocumentWasm { - pub fn rs_get_owner_id(&self) -> Identifier { - self.owner_id.into() - } - - pub fn rs_get_id(&self) -> Identifier { - self.id.into() - } - - pub fn rs_get_data_contract_id(&self) -> Identifier { - self.data_contract_id.into() - } - - pub fn rs_get_entropy(&self) -> Option<[u8; 32]> { - self.entropy - } - - pub fn rs_get_properties(&self) -> BTreeMap { - self.clone().properties - } - - fn set_data_contract_id(&mut self, data_contract_id: &IdentifierWasm) { - self.data_contract_id = *data_contract_id; - } -} diff --git a/packages/wasm-dpp2/src/data_contract/document/mod.rs b/packages/wasm-dpp2/src/data_contract/document/mod.rs index 1088b7fdcb8..22fad5fae9e 100644 --- a/packages/wasm-dpp2/src/data_contract/document/mod.rs +++ b/packages/wasm-dpp2/src/data_contract/document/mod.rs @@ -1,4 +1,3 @@ -pub mod methods; mod model; pub use model::DocumentWasm; diff --git a/packages/wasm-dpp2/src/data_contract/document/model.rs b/packages/wasm-dpp2/src/data_contract/document/model.rs index 441c66725c5..974f9d73ebd 100644 --- a/packages/wasm-dpp2/src/data_contract/document/model.rs +++ b/packages/wasm-dpp2/src/data_contract/document/model.rs @@ -1,104 +1,627 @@ +use crate::data_contract::DataContractWasm; +use crate::enums::platform::PlatformVersionWasm; +use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use dpp::document::{Document, DocumentV0, DocumentV0Getters}; +use crate::serialization; +use crate::utils::ToSerdeJSONExt; +use dpp::document::serialization_traits::{ + DocumentJsonMethodsV0, DocumentPlatformConversionMethodsV0, DocumentPlatformValueMethodsV0, +}; +use dpp::document::{Document, DocumentV0, DocumentV0Getters, DocumentV0Setters}; use dpp::identifier::Identifier; -use dpp::identity::TimestampMillis; -use dpp::platform_value::Value; -use dpp::prelude::{BlockHeight, CoreBlockHeight, Revision}; -use std::collections::BTreeMap; +use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; +use dpp::platform_value::string_encoding::encode; +use dpp::platform_value::{Value, ValueMapHelper}; +use dpp::prelude::Revision; +use dpp::util::entropy_generator; +use dpp::util::entropy_generator::EntropyGenerator; +use dpp::version::PlatformVersion; +use serde::Deserialize; +use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; -#[derive(Clone)] +/// DocumentWasm wraps a Document and adds metadata fields that are not part of the core Document. +#[derive(Clone, serde::Serialize, Deserialize)] #[wasm_bindgen(js_name = Document)] pub struct DocumentWasm { - pub(crate) id: IdentifierWasm, - pub(crate) owner_id: IdentifierWasm, - pub(crate) revision: Option, + #[serde(skip_serializing, skip_deserializing, default = "default_document")] + pub(crate) document: Document, + #[serde(rename = "$dataContractId")] pub(crate) data_contract_id: IdentifierWasm, + #[serde(rename = "$type")] pub(crate) document_type_name: String, - pub(crate) properties: BTreeMap, - pub(crate) created_at: Option, - pub(crate) updated_at: Option, - pub(crate) transferred_at: Option, - pub(crate) created_at_block_height: Option, - pub(crate) updated_at_block_height: Option, - pub(crate) transferred_at_block_height: Option, - pub(crate) created_at_core_block_height: Option, - pub(crate) updated_at_core_block_height: Option, - pub(crate) transferred_at_core_block_height: Option, + #[serde( + rename = "$entropy", + with = "serialization::bytes_b64::option", + skip_serializing_if = "Option::is_none", + default + )] pub(crate) entropy: Option<[u8; 32]>, - pub(crate) creator_id: Option, } -impl From for Document { - fn from(wasm_doc: DocumentWasm) -> Self { - Document::V0(DocumentV0 { - id: wasm_doc.id.into(), - owner_id: wasm_doc.owner_id.into(), - properties: wasm_doc.properties, - revision: wasm_doc.revision, - created_at: wasm_doc.created_at, - updated_at: wasm_doc.updated_at, - transferred_at: wasm_doc.transferred_at, - created_at_block_height: wasm_doc.created_at_block_height, - updated_at_block_height: wasm_doc.updated_at_block_height, - transferred_at_block_height: wasm_doc.transferred_at_block_height, - created_at_core_block_height: wasm_doc.created_at_core_block_height, - updated_at_core_block_height: wasm_doc.updated_at_core_block_height, - transferred_at_core_block_height: wasm_doc.transferred_at_core_block_height, - creator_id: wasm_doc.creator_id.map(Into::into), - }) +fn default_document() -> Document { + Document::V0(DocumentV0::default()) +} + +impl From<&DocumentWasm> for Document { + fn from(wasm_doc: &DocumentWasm) -> Self { + wasm_doc.document.clone() } } -impl From for DocumentWasm { - fn from(doc: Document) -> Self { - DocumentWasm { - id: doc.id().into(), - owner_id: doc.owner_id().into(), - revision: doc.revision(), - data_contract_id: Identifier::default().into(), - document_type_name: "".to_string(), - properties: doc.properties().clone(), - created_at: doc.created_at(), - updated_at: doc.updated_at(), - transferred_at: doc.transferred_at(), - created_at_block_height: doc.created_at_block_height(), - updated_at_block_height: doc.updated_at_block_height(), - transferred_at_block_height: doc.transferred_at_block_height(), - created_at_core_block_height: doc.created_at_core_block_height(), - updated_at_core_block_height: doc.updated_at_core_block_height(), - transferred_at_core_block_height: doc.transferred_at_core_block_height(), - entropy: None, - creator_id: doc.creator_id().map(Into::into), - } +impl From for Document { + fn from(wasm_doc: DocumentWasm) -> Self { + wasm_doc.document } } impl DocumentWasm { - pub fn from_batch( + /// Create a new DocumentWasm with metadata + pub fn new( document: Document, data_contract_id: Identifier, document_type_name: String, entropy: Option<[u8; 32]>, ) -> Self { DocumentWasm { - id: document.id().into(), - owner_id: document.owner_id().into(), - revision: document.revision(), + document, data_contract_id: data_contract_id.into(), document_type_name, - properties: document.properties().clone(), - created_at: document.created_at(), - updated_at: document.updated_at(), - transferred_at: document.transferred_at(), - created_at_block_height: document.created_at_block_height(), - updated_at_block_height: document.updated_at_block_height(), - transferred_at_block_height: document.transferred_at_block_height(), - created_at_core_block_height: document.created_at_core_block_height(), - updated_at_core_block_height: document.updated_at_core_block_height(), - transferred_at_core_block_height: document.transferred_at_core_block_height(), entropy, - creator_id: document.creator_id().map(Into::into), } } + + /// Access the inner document + pub fn inner(&self) -> &Document { + &self.document + } + + /// Mutable access to the inner document + pub fn inner_mut(&mut self) -> &mut Document { + &mut self.document + } + + pub fn set_data_contract_id(&mut self, data_contract_id: &IdentifierWasm) { + self.data_contract_id = *data_contract_id; + } +} + +#[wasm_bindgen(js_class = Document)] +impl DocumentWasm { + #[wasm_bindgen(getter = __type)] + pub fn type_name(&self) -> String { + "Document".to_string() + } + + #[wasm_bindgen(getter = __struct)] + pub fn struct_name() -> String { + "Document".to_string() + } + + #[wasm_bindgen(constructor)] + pub fn constructor( + js_raw_document: JsValue, + js_document_type_name: &str, + js_revision: u64, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_data_contract_id: &JsValue, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_owner_id: &JsValue, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_document_id: &JsValue, + ) -> WasmDppResult { + let data_contract_id: Identifier = IdentifierWasm::try_from(js_data_contract_id)?.into(); + let owner_id: Identifier = IdentifierWasm::try_from(js_owner_id)?.into(); + let revision = Revision::from(js_revision); + let properties = js_raw_document.with_serde_to_platform_value_map()?; + + let entropy = entropy_generator::DefaultEntropyGenerator + .generate() + .map_err(|err| WasmDppError::serialization(err.to_string()))?; + + let document_id: Identifier = match js_document_id.is_undefined() { + true => crate::utils::generate_document_id_v0( + &data_contract_id, + &owner_id, + js_document_type_name, + &entropy, + )?, + false => IdentifierWasm::try_from(js_document_id)?.into(), + }; + + let document = Document::V0(DocumentV0 { + id: document_id, + owner_id, + properties, + revision: Some(revision), + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }); + + Ok(DocumentWasm::new( + document, + data_contract_id, + js_document_type_name.to_string(), + Some(entropy), + )) + } + + #[wasm_bindgen(getter=id)] + pub fn get_id(&self) -> IdentifierWasm { + self.document.id().into() + } + + #[wasm_bindgen(getter=entropy)] + pub fn get_entropy(&self) -> Option> { + self.entropy.map(|entropy| entropy.to_vec()) + } + + #[wasm_bindgen(getter=dataContractId)] + pub fn get_data_contract_id(&self) -> IdentifierWasm { + self.data_contract_id + } + + #[wasm_bindgen(getter=ownerId)] + pub fn get_owner_id(&self) -> IdentifierWasm { + self.document.owner_id().into() + } + + #[wasm_bindgen(getter=properties)] + pub fn get_properties(&self) -> WasmDppResult { + let properties_value = Value::Map( + self.document + .properties() + .iter() + .map(|(k, v)| (Value::Text(k.clone()), v.clone())) + .collect(), + ); + serialization::platform_value_to_object(&properties_value) + } + + #[wasm_bindgen(getter=revision)] + pub fn get_revision(&self) -> Option { + self.document.revision() + } + + #[wasm_bindgen(getter=createdAt)] + pub fn get_created_at(&self) -> Option { + self.document.created_at() + } + + #[wasm_bindgen(getter=updatedAt)] + pub fn get_updated_at(&self) -> Option { + self.document.updated_at() + } + + #[wasm_bindgen(getter=transferredAt)] + pub fn get_transferred_at(&self) -> Option { + self.document.transferred_at() + } + + #[wasm_bindgen(getter=createdAtBlockHeight)] + pub fn get_created_at_block_height(&self) -> Option { + self.document.created_at_block_height() + } + + #[wasm_bindgen(getter=updatedAtBlockHeight)] + pub fn get_updated_at_block_height(&self) -> Option { + self.document.updated_at_block_height() + } + + #[wasm_bindgen(getter=transferredAtBlockHeight)] + pub fn get_transferred_at_block_height(&self) -> Option { + self.document.transferred_at_block_height() + } + + #[wasm_bindgen(getter=createdAtCoreBlockHeight)] + pub fn get_created_at_core_block_height(&self) -> Option { + self.document.created_at_core_block_height() + } + + #[wasm_bindgen(getter=updatedAtCoreBlockHeight)] + pub fn get_updated_at_core_block_height(&self) -> Option { + self.document.updated_at_core_block_height() + } + + #[wasm_bindgen(getter=transferredAtCoreBlockHeight)] + pub fn get_transferred_at_core_block_height(&self) -> Option { + self.document.transferred_at_core_block_height() + } + + #[wasm_bindgen(getter=documentTypeName)] + pub fn get_document_type_name(&self) -> String { + self.document_type_name.clone() + } + + #[wasm_bindgen(setter=id)] + pub fn set_id( + &mut self, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] id: &JsValue, + ) -> WasmDppResult<()> { + let identifier: Identifier = IdentifierWasm::try_from(id)?.into(); + self.document.set_id(identifier); + Ok(()) + } + + #[wasm_bindgen(setter=entropy)] + pub fn set_entropy(&mut self, entropy: JsValue) -> WasmDppResult<()> { + if entropy.is_undefined() { + self.entropy = None; + return Ok(()); + } + + let value = entropy.with_serde_to_platform_value()?; + let bytes = value.as_bytes().ok_or_else(|| { + WasmDppError::invalid_argument("Entropy must be provided as a byte array") + })?; + + let mut entropy_bytes = [0u8; 32]; + let len = bytes.len().min(32); + entropy_bytes[..len].copy_from_slice(&bytes[..len]); + self.entropy = Some(entropy_bytes); + + Ok(()) + } + + #[wasm_bindgen(setter=dataContractId)] + pub fn set_js_data_contract_id( + &mut self, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_contract_id: &JsValue, + ) -> WasmDppResult<()> { + self.data_contract_id = IdentifierWasm::try_from(js_contract_id)?; + Ok(()) + } + + #[wasm_bindgen(setter=ownerId)] + pub fn set_owner_id( + &mut self, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] id: &JsValue, + ) -> WasmDppResult<()> { + let identifier: Identifier = IdentifierWasm::try_from(id)?.into(); + self.document.set_owner_id(identifier); + Ok(()) + } + + #[wasm_bindgen(setter=properties)] + pub fn set_properties(&mut self, properties: JsValue) -> WasmDppResult<()> { + let props = properties.with_serde_to_platform_value_map()?; + *self.document.properties_mut() = props; + Ok(()) + } + + #[wasm_bindgen(setter=revision)] + pub fn set_revision(&mut self, revision: Option) { + self.document.set_revision(revision); + } + + #[wasm_bindgen(setter=createdAt)] + pub fn set_created_at(&mut self, created_at: Option) { + self.document.set_created_at(created_at); + } + + #[wasm_bindgen(setter=updatedAt)] + pub fn set_updated_at(&mut self, updated_at: Option) { + self.document.set_updated_at(updated_at); + } + + #[wasm_bindgen(setter=transferredAt)] + pub fn set_transferred_at(&mut self, transferred_at: Option) { + self.document.set_transferred_at(transferred_at); + } + + #[wasm_bindgen(setter=createdAtBlockHeight)] + pub fn set_created_at_block_height(&mut self, created_at_block_height: Option) { + self.document + .set_created_at_block_height(created_at_block_height); + } + + #[wasm_bindgen(setter=updatedAtBlockHeight)] + pub fn set_updated_at_block_height(&mut self, updated_at_block_height: Option) { + self.document + .set_updated_at_block_height(updated_at_block_height); + } + + #[wasm_bindgen(setter=transferredAtBlockHeight)] + pub fn set_transferred_at_block_height(&mut self, transferred_at_block_height: Option) { + self.document + .set_transferred_at_block_height(transferred_at_block_height); + } + + #[wasm_bindgen(setter=createdAtCoreBlockHeight)] + pub fn set_created_at_core_block_height(&mut self, created_at_core_block_height: Option) { + self.document + .set_created_at_core_block_height(created_at_core_block_height); + } + + #[wasm_bindgen(setter=updatedAtCoreBlockHeight)] + pub fn set_updated_at_core_block_height(&mut self, updated_at_core_block_height: Option) { + self.document + .set_updated_at_core_block_height(updated_at_core_block_height); + } + + #[wasm_bindgen(setter=transferredAtCoreBlockHeight)] + pub fn set_transferred_at_core_block_height( + &mut self, + transferred_at_core_block_height: Option, + ) { + self.document + .set_transferred_at_core_block_height(transferred_at_core_block_height); + } + + #[wasm_bindgen(setter=documentTypeName)] + pub fn set_document_type_name(&mut self, document_type_name: &str) { + self.document_type_name = document_type_name.to_string(); + } + + /// Convert to a JS object with binary fields as Uint8Array. + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + let mut map = self.document.to_map_value()?; + // Add metadata fields not in core Document + let data_contract_id: Identifier = self.data_contract_id.into(); + map.insert( + "$dataContractId".to_string(), + Value::Identifier(data_contract_id.into_buffer()), + ); + map.insert( + "$type".to_string(), + Value::Text(self.document_type_name.clone()), + ); + if let Some(entropy) = self.entropy { + map.insert("$entropy".to_string(), Value::Bytes(entropy.to_vec())); + } + serialization::platform_value_to_object(&Value::Map( + map.into_iter().map(|(k, v)| (Value::Text(k), v)).collect(), + )) + } + + /// Create a Document from a JS object. + #[wasm_bindgen(js_name = fromObject)] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + let platform_value = serialization::js_value_to_platform_value(&js_value)?; + + let Value::Map(mut map) = platform_value else { + return Err(WasmDppError::invalid_argument("Expected an object")); + }; + + // Extract metadata fields using ValueMapHelper trait methods + let data_contract_id = map + .remove_optional_key("$dataContractId") + .ok_or_else(|| WasmDppError::invalid_argument("Missing $dataContractId"))? + .into_identifier() + .map_err(|e| WasmDppError::invalid_argument(e.to_string()))?; + + let document_type_name = map + .remove_optional_key("$type") + .ok_or_else(|| WasmDppError::invalid_argument("Missing $type"))? + .into_text() + .map_err(|e| WasmDppError::invalid_argument(e.to_string()))?; + + let entropy = map.remove_optional_key("$entropy").and_then(|v| { + v.into_bytes().ok().and_then(|bytes| { + if bytes.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Some(arr) + } else { + None + } + }) + }); + + // Create Document from remaining fields + let document = Document::from_platform_value(Value::Map(map), PlatformVersion::latest())?; + + Ok(DocumentWasm::new( + document, + data_contract_id, + document_type_name, + entropy, + )) + } + + /// Convert to a JSON-compatible JS object with binary fields as strings. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + // Get document fields as JSON + let mut json_value = self.document.to_json(PlatformVersion::latest())?; + + // Serialize wrapper fields using serde and merge into document JSON + let wrapper_json = + serde_json::to_value(self).map_err(|e| WasmDppError::serialization(e.to_string()))?; + + let obj = json_value.as_object_mut().ok_or_else(|| { + WasmDppError::serialization("Expected JSON object from Document::to_json") + })?; + + if let serde_json::Value::Object(wrapper_obj) = wrapper_json { + for (key, value) in wrapper_obj { + obj.insert(key, value); + } + } + + serialization::json_to_js_value(&json_value) + } + + /// Create a Document from a JSON object. + /// JSON format has identifiers as base58 strings. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + let mut json_value = serialization::js_value_to_json(&js_value)?; + + // Deserialize wrapper fields using serde + let mut wrapper: DocumentWasm = serde_json::from_value(json_value.clone()) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + + // Remove wrapper fields from JSON before passing to Document::from_json_value + if let serde_json::Value::Object(ref mut obj) = json_value { + obj.remove("$dataContractId"); + obj.remove("$type"); + obj.remove("$entropy"); + } + + // Create Document from remaining fields + wrapper.document = + Document::from_json_value::(json_value, PlatformVersion::latest())?; + + Ok(wrapper) + } + + #[wasm_bindgen(js_name=toBytes)] + pub fn to_bytes( + &self, + data_contract: &DataContractWasm, + js_platform_version: JsValue, + ) -> WasmDppResult> { + let platform_version = match js_platform_version.is_undefined() { + true => PlatformVersionWasm::default(), + false => PlatformVersionWasm::try_from(js_platform_version)?, + }; + + let document_type_ref = data_contract + .get_document_type_ref_by_name(self.get_document_type_name()) + .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; + + self.document + .serialize( + document_type_ref, + &data_contract.clone().into(), + &platform_version.into(), + ) + .map_err(Into::into) + } + + #[wasm_bindgen(js_name=toHex)] + pub fn to_hex( + &self, + data_contract: &DataContractWasm, + js_platform_version: JsValue, + ) -> WasmDppResult { + Ok(encode( + self.to_bytes(data_contract, js_platform_version)? + .as_slice(), + Hex, + )) + } + + #[wasm_bindgen(js_name=toBase64)] + pub fn to_base64( + &self, + data_contract: &DataContractWasm, + js_platform_version: JsValue, + ) -> WasmDppResult { + Ok(encode( + self.to_bytes(data_contract, js_platform_version)? + .as_slice(), + Base64, + )) + } + + #[wasm_bindgen(js_name=fromBytes)] + pub fn from_bytes( + bytes: Vec, + data_contract: &DataContractWasm, + type_name: String, + js_platform_version: JsValue, + ) -> WasmDppResult { + let platform_version = match js_platform_version.is_undefined() { + true => PlatformVersionWasm::default(), + false => PlatformVersionWasm::try_from(js_platform_version)?, + }; + + let document_type_ref = data_contract + .get_document_type_ref_by_name(type_name.clone()) + .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; + + let document = Document::from_bytes( + bytes.as_slice(), + document_type_ref, + &platform_version.into(), + )?; + + Ok(DocumentWasm::new( + document, + data_contract.get_id().into(), + type_name, + None, + )) + } + + #[wasm_bindgen(js_name=fromHex)] + pub fn from_hex( + hex: String, + data_contract: &DataContractWasm, + type_name: String, + js_platform_version: JsValue, + ) -> WasmDppResult { + use dpp::platform_value::string_encoding::decode; + DocumentWasm::from_bytes( + decode(hex.as_str(), Hex) + .map_err(|err| WasmDppError::serialization(err.to_string()))?, + data_contract, + type_name, + js_platform_version, + ) + } + + #[wasm_bindgen(js_name=fromBase64)] + pub fn from_base64( + base64: String, + data_contract: &DataContractWasm, + type_name: String, + js_platform_version: JsValue, + ) -> WasmDppResult { + use dpp::platform_value::string_encoding::decode; + DocumentWasm::from_bytes( + decode(base64.as_str(), Base64) + .map_err(|err| WasmDppError::serialization(err.to_string()))?, + data_contract, + type_name, + js_platform_version, + ) + } + + #[wasm_bindgen(js_name=generateId)] + pub fn generate_id( + js_document_type_name: &str, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_owner_id: &JsValue, + #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] + js_data_contract_id: &JsValue, + opt_entropy: Option>, + ) -> WasmDppResult> { + let owner_id: Identifier = IdentifierWasm::try_from(js_owner_id)?.into(); + let data_contract_id: Identifier = IdentifierWasm::try_from(js_data_contract_id)?.into(); + + let entropy: [u8; 32] = match opt_entropy { + Some(entropy_vec) => { + let mut entropy = [0u8; 32]; + let bytes = entropy_vec.as_slice(); + let len = bytes.len().min(32); + entropy[..len].copy_from_slice(&bytes[..len]); + entropy + } + None => entropy_generator::DefaultEntropyGenerator + .generate() + .map_err(|err| WasmDppError::serialization(err.to_string()))?, + }; + + let identifier = crate::utils::generate_document_id_v0( + &data_contract_id, + &owner_id, + js_document_type_name, + &entropy, + )?; + + Ok(identifier.to_vec()) + } } diff --git a/packages/wasm-dpp2/src/data_contract/model.rs b/packages/wasm-dpp2/src/data_contract/model.rs index e9f55376ed3..6b955b24f64 100644 --- a/packages/wasm-dpp2/src/data_contract/model.rs +++ b/packages/wasm-dpp2/src/data_contract/model.rs @@ -1,10 +1,10 @@ use crate::enums::platform::PlatformVersionWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::serialization; use crate::tokens::configuration::TokenConfigurationWasm; use crate::tokens::configuration::group::GroupWasm; use crate::utils::{IntoWasm, JsValueExt, ToSerdeJSONExt}; -use dpp::dashcore::hashes::serde::Serialize; use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; use dpp::data_contract::accessors::v1::{DataContractV1Getters, DataContractV1Setters}; use dpp::data_contract::config::DataContractConfig; @@ -17,7 +17,7 @@ use dpp::data_contract::schema::DataContractSchemaMethodsV0; use dpp::data_contract::{ DataContract, GroupContractPosition, TokenConfiguration, TokenContractPosition, }; -use dpp::platform_value::string_encoding::Encoding::{Base58, Base64, Hex}; +use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; use dpp::platform_value::string_encoding::{decode, encode}; use dpp::platform_value::{Value, ValueMap}; use dpp::prelude::{Identifier, IdentityNonce}; @@ -104,14 +104,10 @@ impl DataContractWasm { full_validation: bool, js_platform_version: JsValue, ) -> WasmDppResult { - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - let owner_id: IdentifierWasm = js_owner_id.clone().try_into()?; - let owner_id_value = Value::from(owner_id.get_base58()); - - let schema: Value = serde_wasm_bindgen::from_value(js_schema) - .map_err(|err| WasmDppError::serialization(err.to_string()))?; + // Use object format deserializer (handles BigInt/Uint8Array) + let schema: Value = serialization::platform_value_from_object(js_schema)?; let tokens: BTreeMap = match js_tokens.is_undefined() { @@ -132,24 +128,21 @@ impl DataContractWasm { .to_string(), ); - let definitions = js_definitions - .map(|definitions| serde_wasm_bindgen::from_value(definitions.into())) - .transpose() - .map_err(|err| WasmDppError::serialization(err.to_string()))?; + let definitions: Option = js_definitions + .map(|definitions| serialization::platform_value_from_object(definitions.into())) + .transpose()?; let definitions_value = Value::from(definitions); let data_contract_id = DataContract::generate_data_contract_id_v0(owner_id.to_bytes(), identity_nonce); - let data_contract_id_value = Value::from(data_contract_id.to_string(Base58)); + let data_contract_id_value = Value::Identifier(data_contract_id.to_buffer()); let config = DataContractConfig::default_for_version(&platform_version.clone())?; - let config_value = config - .serialize(&serializer) - .map_err(|e| WasmDppError::serialization(e.to_string()))? - .with_serde_to_platform_value_map()?; + let config_value: Value = dpp::platform_value::to_value(config) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; let mut contract_value = Value::Map(ValueMap::new()); @@ -162,15 +155,20 @@ impl DataContractWasm { .map_err(|err| WasmDppError::serialization(err.to_string()))?; contract_value - .set_value("config", Value::from(config_value)) + .set_value("config", config_value) .map_err(|err| WasmDppError::serialization(err.to_string()))?; contract_value .set_value("version", Value::from(1u16)) .map_err(|err| WasmDppError::serialization(err.to_string()))?; + let owner_id_bytes: [u8; 32] = owner_id + .to_bytes() + .try_into() + .map_err(|_| WasmDppError::invalid_argument("Invalid owner ID length"))?; + contract_value - .set_value("ownerId", owner_id_value) + .set_value("ownerId", Value::Identifier(owner_id_bytes)) .map_err(|err| WasmDppError::serialization(err.to_string()))?; contract_value @@ -196,8 +194,27 @@ impl DataContractWasm { Ok(DataContractWasm(data_contract_with_tokens)) } - #[wasm_bindgen(js_name = "fromValue")] - pub fn from_value( + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json( + js_value: JsValue, + full_validation: bool, + js_platform_version: JsValue, + ) -> WasmDppResult { + let platform_version = match js_platform_version.is_undefined() { + true => PlatformVersionWasm::default(), + false => PlatformVersionWasm::try_from(js_platform_version)?, + }; + + let json_value = serialization::js_value_to_json(&js_value)?; + + let contract = + DataContract::from_json(json_value, full_validation, &platform_version.into())?; + + Ok(DataContractWasm(contract)) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object( js_value: JsValue, full_validation: bool, js_platform_version: JsValue, @@ -207,9 +224,11 @@ impl DataContractWasm { false => PlatformVersionWasm::try_from(js_platform_version)?, }; - let value = js_value.with_serde_to_platform_value()?; + let platform_value: Value = serialization::platform_value_from_object(js_value)?; - let contract = DataContract::from_value(value, full_validation, &platform_version.into())?; + let contract = + DataContract::from_value(platform_value, full_validation, &platform_version.into()) + .map_err(WasmDppError::from)?; Ok(DataContractWasm(contract)) } @@ -275,7 +294,7 @@ impl DataContractWasm { Ok(encode(self.to_bytes(js_platform_version)?.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self, js_platform_version: JsValue) -> WasmDppResult { Ok(encode( self.to_bytes(js_platform_version)?.as_slice(), @@ -283,30 +302,20 @@ impl DataContractWasm { )) } - #[wasm_bindgen(js_name = "toValue")] - pub fn to_value(&self, js_platform_version: JsValue) -> WasmDppResult { + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self, js_platform_version: JsValue) -> WasmDppResult { let platform_version = match js_platform_version.is_undefined() { true => PlatformVersionWasm::default(), false => PlatformVersionWasm::try_from(js_platform_version)?, }; - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - - self.0 - .clone() - .to_value(&platform_version.into())? - .serialize(&serializer) - .map_err(|e| WasmDppError::serialization(e.to_string())) + let value = self.0.clone().to_value(&platform_version.into())?; + serialization::platform_value_to_object(&value) } #[wasm_bindgen(js_name = "getSchemas")] pub fn get_schemas(&self) -> WasmDppResult { - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - - self.0 - .document_schemas() - .serialize(&serializer) - .map_err(|e| WasmDppError::serialization(e.to_string())) + serialization::to_object(&self.0.document_schemas()) } #[wasm_bindgen(getter = "version")] @@ -326,10 +335,7 @@ impl DataContractWasm { #[wasm_bindgen(js_name = "getConfig")] pub fn get_config(&self) -> WasmDppResult { - self.0 - .config() - .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|e| WasmDppError::serialization(e.to_string())) + serialization::to_object(self.0.config()) } #[wasm_bindgen(getter = "tokens")] @@ -509,9 +515,7 @@ impl DataContractWasm { }; let json = self.0.to_json(&platform_version.into())?; - - json.serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|e| WasmDppError::serialization(e.to_string())) + serialization::to_json(&json) } #[wasm_bindgen(js_name = "generateId")] diff --git a/packages/wasm-dpp2/src/data_contract/transitions/create.rs b/packages/wasm-dpp2/src/data_contract/transitions/create.rs index 8eefbcb3db1..dbdd2bbd3e7 100644 --- a/packages/wasm-dpp2/src/data_contract/transitions/create.rs +++ b/packages/wasm-dpp2/src/data_contract/transitions/create.rs @@ -1,6 +1,7 @@ use crate::data_contract::DataContractWasm; use crate::enums::platform::PlatformVersionWasm; use crate::error::{WasmDppError, WasmDppResult}; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; @@ -101,7 +102,7 @@ impl DataContractCreateTransitionWasm { Ok(encode(self.to_bytes()?.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { Ok(encode(self.to_bytes()?.as_slice(), Base64)) } @@ -194,3 +195,8 @@ impl DataContractCreateTransitionWasm { } } } + +impl_wasm_conversions!( + DataContractCreateTransitionWasm, + DataContractCreateTransition +); diff --git a/packages/wasm-dpp2/src/data_contract/transitions/update.rs b/packages/wasm-dpp2/src/data_contract/transitions/update.rs index 60a793267ec..2ba663a2019 100644 --- a/packages/wasm-dpp2/src/data_contract/transitions/update.rs +++ b/packages/wasm-dpp2/src/data_contract/transitions/update.rs @@ -1,6 +1,7 @@ use crate::data_contract::DataContractWasm; use crate::enums::platform::PlatformVersionWasm; use crate::error::{WasmDppError, WasmDppResult}; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; @@ -88,7 +89,7 @@ impl DataContractUpdateTransitionWasm { Ok(encode(self.to_bytes()?.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { Ok(encode(self.to_bytes()?.as_slice(), Base64)) } @@ -181,3 +182,8 @@ impl DataContractUpdateTransitionWasm { } } } + +impl_wasm_conversions!( + DataContractUpdateTransitionWasm, + DataContractUpdateTransition +); diff --git a/packages/wasm-dpp2/src/epoch/extended_epoch_info.rs b/packages/wasm-dpp2/src/epoch/extended_epoch_info.rs index f0b7f449516..f4e8af8f0a7 100644 --- a/packages/wasm-dpp2/src/epoch/extended_epoch_info.rs +++ b/packages/wasm-dpp2/src/epoch/extended_epoch_info.rs @@ -1,3 +1,4 @@ +use crate::impl_wasm_conversions; use dpp::block::extended_epoch_info::ExtendedEpochInfo; use dpp::block::extended_epoch_info::v0::{ExtendedEpochInfoV0, ExtendedEpochInfoV0Getters}; use js_sys::BigInt; @@ -123,3 +124,5 @@ impl ExtendedEpochInfoWasm { self.v0_mut().protocol_version = protocol_version; } } + +impl_wasm_conversions!(ExtendedEpochInfoWasm, ExtendedEpochInfo); diff --git a/packages/wasm-dpp2/src/epoch/finalized_epoch_info.rs b/packages/wasm-dpp2/src/epoch/finalized_epoch_info.rs index b003683fff8..2b23b6254de 100644 --- a/packages/wasm-dpp2/src/epoch/finalized_epoch_info.rs +++ b/packages/wasm-dpp2/src/epoch/finalized_epoch_info.rs @@ -272,9 +272,9 @@ impl FinalizedEpochInfoWasm { } #[wasm_bindgen(setter = "blockProposers")] - pub fn set_block_proposers(&mut self, js_block_proposers: &JsValue) -> WasmDppResult<()> { - let block_proposers = block_proposers_from_js(js_block_proposers)?; - self.v0_mut().block_proposers = block_proposers; + pub fn set_block_proposers(&mut self, block_proposers: &JsValue) -> WasmDppResult<()> { + let block_proposers_map = block_proposers_from_js(block_proposers)?; + self.v0_mut().block_proposers = block_proposers_map; Ok(()) } @@ -288,3 +288,5 @@ impl FinalizedEpochInfoWasm { self.v0_mut().protocol_version = protocol_version; } } + +crate::impl_wasm_conversions!(FinalizedEpochInfoWasm, FinalizedEpochInfo); diff --git a/packages/wasm-dpp2/src/group/action.rs b/packages/wasm-dpp2/src/group/action.rs index 7886cc375d8..e97280a4052 100644 --- a/packages/wasm-dpp2/src/group/action.rs +++ b/packages/wasm-dpp2/src/group/action.rs @@ -1,5 +1,6 @@ use crate::group::action_event::GroupActionEventWasm; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use dpp::data_contract::TokenContractPosition; use dpp::group::group_action::{GroupAction, GroupActionAccessors}; use wasm_bindgen::prelude::wasm_bindgen; @@ -52,3 +53,5 @@ impl GroupActionWasm { GroupActionEventWasm::from(self.0.event().clone()) } } + +impl_wasm_conversions!(GroupActionWasm, GroupAction); diff --git a/packages/wasm-dpp2/src/group/action_event.rs b/packages/wasm-dpp2/src/group/action_event.rs index 3a9315addb5..01aa19d350d 100644 --- a/packages/wasm-dpp2/src/group/action_event.rs +++ b/packages/wasm-dpp2/src/group/action_event.rs @@ -1,7 +1,15 @@ use crate::group::token_event::TokenEventWasm; +use crate::impl_wasm_conversions; use dpp::group::action_event::GroupActionEvent; use wasm_bindgen::prelude::wasm_bindgen; +/// TypeScript enum for GroupActionEvent variants +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GroupActionEventVariant { + TokenEvent = 0, +} + #[derive(Clone, Debug, PartialEq)] #[wasm_bindgen(js_name = "GroupActionEvent")] pub struct GroupActionEventWasm(GroupActionEvent); @@ -31,9 +39,9 @@ impl GroupActionEventWasm { } #[wasm_bindgen(getter = "variant")] - pub fn variant(&self) -> String { + pub fn variant(&self) -> GroupActionEventVariant { match &self.0 { - GroupActionEvent::TokenEvent(_) => "TokenEvent".to_string(), + GroupActionEvent::TokenEvent(_) => GroupActionEventVariant::TokenEvent, } } @@ -54,3 +62,5 @@ impl GroupActionEventWasm { self.0.public_note().map(|note| note.to_string()) } } + +impl_wasm_conversions!(GroupActionEventWasm, GroupActionEvent); diff --git a/packages/wasm-dpp2/src/group/token_event.rs b/packages/wasm-dpp2/src/group/token_event.rs index d72e479d015..6ff83d65b1c 100644 --- a/packages/wasm-dpp2/src/group/token_event.rs +++ b/packages/wasm-dpp2/src/group/token_event.rs @@ -1,19 +1,27 @@ -use crate::enums::token::emergency_action::TokenEmergencyActionWasm; -use crate::error::{WasmDppError, WasmDppResult}; -use crate::identifier::IdentifierWasm; -use crate::state_transitions::batch::token_pricing_schedule::TokenPricingScheduleWasm; -use crate::tokens::configuration_change_item::TokenConfigurationChangeItemWasm; -use crate::tokens::encrypted_note::private_encrypted_note::PrivateEncryptedNoteWasm; -use crate::tokens::encrypted_note::shared_encrypted_note::SharedEncryptedNoteWasm; -use crate::utils::JsValueExt; +use crate::impl_wasm_conversions; use dpp::tokens::token_event::TokenEvent; -use js_sys::{BigInt, Object, Reflect}; -use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; +/// TypeScript enum for TokenEvent variants +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TokenEventVariant { + Mint = 0, + Burn = 1, + Freeze = 2, + Unfreeze = 3, + DestroyFrozenFunds = 4, + Transfer = 5, + Claim = 6, + EmergencyAction = 7, + ConfigUpdate = 8, + ChangePriceForDirectPurchase = 9, + DirectPurchase = 10, +} + #[derive(Clone, Debug, PartialEq)] #[wasm_bindgen(js_name = "TokenEvent")] -pub struct TokenEventWasm(TokenEvent); +pub struct TokenEventWasm(pub(crate) TokenEvent); impl From for TokenEventWasm { fn from(event: TokenEvent) -> Self { @@ -40,157 +48,23 @@ impl TokenEventWasm { } #[wasm_bindgen(getter = "variant")] - pub fn variant(&self) -> String { - match &self.0 { - TokenEvent::Mint(..) => "Mint", - TokenEvent::Burn(..) => "Burn", - TokenEvent::Freeze(..) => "Freeze", - TokenEvent::Unfreeze(..) => "Unfreeze", - TokenEvent::DestroyFrozenFunds(..) => "DestroyFrozenFunds", - TokenEvent::Transfer(..) => "Transfer", - TokenEvent::Claim(..) => "Claim", - TokenEvent::EmergencyAction(..) => "EmergencyAction", - TokenEvent::ConfigUpdate(..) => "ConfigUpdate", - TokenEvent::ChangePriceForDirectPurchase(..) => "ChangePriceForDirectPurchase", - TokenEvent::DirectPurchase(..) => "DirectPurchase", - } - .to_string() - } - - #[wasm_bindgen(js_name = "toObject")] - pub fn to_object(&self) -> WasmDppResult { - let object = Object::new(); - set_property(&object, "variant", &JsValue::from_str(&self.variant()))?; - + pub fn variant(&self) -> TokenEventVariant { match &self.0 { - TokenEvent::Mint(amount, recipient, note) => { - set_amount(&object, *amount)?; - set_property( - &object, - "recipient", - &JsValue::from(IdentifierWasm::from(*recipient)), - )?; - set_note(&object, note)?; - } - TokenEvent::Burn(amount, burn_from, note) => { - set_amount(&object, *amount)?; - set_property( - &object, - "burnFrom", - &JsValue::from(IdentifierWasm::from(*burn_from)), - )?; - set_note(&object, note)?; - } - TokenEvent::Freeze(identity, note) | TokenEvent::Unfreeze(identity, note) => { - set_property( - &object, - "identity", - &JsValue::from(IdentifierWasm::from(*identity)), - )?; - set_note(&object, note)?; - } - TokenEvent::DestroyFrozenFunds(identity, amount, note) => { - set_property( - &object, - "identity", - &JsValue::from(IdentifierWasm::from(*identity)), - )?; - set_amount(&object, *amount)?; - set_note(&object, note)?; - } - TokenEvent::Transfer(recipient, note, shared, private, amount) => { - set_property( - &object, - "recipient", - &JsValue::from(IdentifierWasm::from(*recipient)), - )?; - set_note(&object, note)?; - - match shared { - Some(value) => set_property( - &object, - "sharedEncryptedNote", - &JsValue::from(SharedEncryptedNoteWasm::from(value.clone())), - )?, - None => set_property(&object, "sharedEncryptedNote", &JsValue::NULL)?, - } - - match private { - Some(value) => set_property( - &object, - "privateEncryptedNote", - &JsValue::from(PrivateEncryptedNoteWasm::from(value.clone())), - )?, - None => set_property(&object, "privateEncryptedNote", &JsValue::NULL)?, - } - - set_amount(&object, *amount)?; - } - TokenEvent::Claim(distribution, amount, note) => { - let distribution_js = - serde_wasm_bindgen::to_value(distribution).map_err(|err| { - WasmDppError::serialization(format!( - "unable to serialize token distribution recipient: {}", - err - )) - })?; - set_property(&object, "distribution", &distribution_js)?; - set_amount(&object, *amount)?; - set_note(&object, note)?; - } - TokenEvent::EmergencyAction(action, note) => { - set_property( - &object, - "action", - &JsValue::from(TokenEmergencyActionWasm::from(*action)), - )?; - set_note(&object, note)?; - } - TokenEvent::ConfigUpdate(change, note) => { - set_property( - &object, - "change", - &JsValue::from(TokenConfigurationChangeItemWasm::from(change.clone())), - )?; - set_note(&object, note)?; - } - TokenEvent::ChangePriceForDirectPurchase(schedule, note) => { - match schedule { - Some(s) => set_property( - &object, - "pricingSchedule", - &JsValue::from(TokenPricingScheduleWasm::from(s.clone())), - )?, - None => set_property(&object, "pricingSchedule", &JsValue::NULL)?, - } - set_note(&object, note)?; - } - TokenEvent::DirectPurchase(amount, credits) => { - set_amount(&object, *amount)?; - set_property(&object, "credits", &BigInt::from(*credits))?; + TokenEvent::Mint(..) => TokenEventVariant::Mint, + TokenEvent::Burn(..) => TokenEventVariant::Burn, + TokenEvent::Freeze(..) => TokenEventVariant::Freeze, + TokenEvent::Unfreeze(..) => TokenEventVariant::Unfreeze, + TokenEvent::DestroyFrozenFunds(..) => TokenEventVariant::DestroyFrozenFunds, + TokenEvent::Transfer(..) => TokenEventVariant::Transfer, + TokenEvent::Claim(..) => TokenEventVariant::Claim, + TokenEvent::EmergencyAction(..) => TokenEventVariant::EmergencyAction, + TokenEvent::ConfigUpdate(..) => TokenEventVariant::ConfigUpdate, + TokenEvent::ChangePriceForDirectPurchase(..) => { + TokenEventVariant::ChangePriceForDirectPurchase } + TokenEvent::DirectPurchase(..) => TokenEventVariant::DirectPurchase, } - - Ok(object.into()) } } -fn set_amount(object: &Object, amount: u64) -> WasmDppResult<()> { - set_property(object, "amount", &BigInt::from(amount)) -} - -fn set_note(object: &Object, note: &Option) -> WasmDppResult<()> { - match note { - Some(value) => set_property(object, "note", &JsValue::from_str(value)), - None => set_property(object, "note", &JsValue::NULL), - } -} - -fn set_property(object: &Object, key: &str, value: &JsValue) -> WasmDppResult<()> { - Reflect::set(object, &JsValue::from_str(key), value).map_err(|err| { - let message = err.error_message(); - WasmDppError::generic(format!("unable to set property '{key}': {message}")) - })?; - - Ok(()) -} +impl_wasm_conversions!(TokenEventWasm, TokenEvent); diff --git a/packages/wasm-dpp2/src/identifier.rs b/packages/wasm-dpp2/src/identifier.rs index 74c3cf579ed..5a682f54875 100644 --- a/packages/wasm-dpp2/src/identifier.rs +++ b/packages/wasm-dpp2/src/identifier.rs @@ -5,7 +5,7 @@ use dpp::platform_value::string_encoding::decode; use dpp::prelude::Identifier; use js_sys::Uint8Array; use serde::de::{self, Error, MapAccess, SeqAccess, Visitor}; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value as JsonValue; use std::fmt; use wasm_bindgen::prelude::*; @@ -176,7 +176,29 @@ impl<'de> Deserialize<'de> for IdentifierWasm { where D: Deserializer<'de>, { - deserializer.deserialize_any(IdentifierWasmVisitor) + if deserializer.is_human_readable() { + // JSON/human-readable: expect Base58 string + let s = String::deserialize(deserializer)?; + IdentifierWasm::try_from(s.as_str()).map_err(D::Error::custom) + } else { + // Binary/WASM: expect bytes or any compatible representation + deserializer.deserialize_any(IdentifierWasmVisitor) + } + } +} + +impl Serialize for IdentifierWasm { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + // JSON/human-readable: serialize as Base58 string + serializer.serialize_str(&self.to_base58()) + } else { + // Binary/WASM: serialize as bytes (becomes Uint8Array) + serializer.serialize_bytes(&self.0.to_vec()) + } } } @@ -195,18 +217,25 @@ impl IdentifierWasm { #[wasm_bindgen(constructor)] pub fn new( #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_identifier: &JsValue, + identifier: &JsValue, ) -> WasmDppResult { - IdentifierWasm::try_from(js_identifier) + IdentifierWasm::try_from(identifier) } - #[wasm_bindgen(js_name = "base58")] - pub fn get_base58(&self) -> String { + #[wasm_bindgen(js_name = "toBase58")] + pub fn to_base58(&self) -> String { self.0.to_string(Base58) } - #[wasm_bindgen(js_name = "base64")] - pub fn get_base64(&self) -> String { + /// Returns the identifier as a Base58 string for JSON serialization. + /// This method is called automatically when the object is serialized to JSON. + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> String { + self.to_base58() + } + + #[wasm_bindgen(js_name = "toBase64")] + pub fn to_base64(&self) -> String { self.0.to_string(Base64) } diff --git a/packages/wasm-dpp2/src/identity/model.rs b/packages/wasm-dpp2/src/identity/model.rs index ce5cbac87f4..103575d3903 100644 --- a/packages/wasm-dpp2/src/identity/model.rs +++ b/packages/wasm-dpp2/src/identity/model.rs @@ -1,17 +1,15 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; use crate::identity::public_key::IdentityPublicKeyWasm; +use crate::serialization; use dpp::identity::accessors::{IdentityGettersV0, IdentitySettersV0}; -use dpp::identity::{self, Identity, KeyID}; -use dpp::platform_value::ReplacementType; +use dpp::identity::{Identity, KeyID}; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; use dpp::platform_value::string_encoding::{decode, encode}; -use dpp::prelude::{Identifier, IdentityPublicKey}; +use dpp::prelude::Identifier; use dpp::serialization::{PlatformDeserializable, PlatformSerializable, ValueConvertible}; -use dpp::version::PlatformVersion; -use serde_json::Value as JsonValue; -use serde_wasm_bindgen::to_value; -use std::collections::BTreeMap; +use dpp::version::{PlatformVersion, TryFromPlatformVersioned}; +use std::convert::TryFrom; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -134,49 +132,35 @@ impl IdentityWasm { Ok(encode(bytes.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; Ok(encode(bytes.as_slice(), Base64)) } - fn cleaned_json_value(&self) -> WasmDppResult { - let mut value = self.0.to_object()?; - - value - .replace_at_paths( - identity::IDENTIFIER_FIELDS_RAW_OBJECT, - ReplacementType::TextBase58, - ) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - - if let Some(public_keys) = value - .get_optional_array_mut_ref(identity::property_names::PUBLIC_KEYS) - .map_err(|e| WasmDppError::serialization(e.to_string()))? - { - for key in public_keys.iter_mut() { - key.replace_at_paths( - identity::identity_public_key::BINARY_DATA_FIELDS, - ReplacementType::TextBase64, - ) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - } - } - - value - .try_into_validating_json() - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = "toObject")] pub fn to_object(&self) -> WasmDppResult { - let json_value = self.cleaned_json_value()?; - to_value(&json_value).map_err(|e| WasmDppError::serialization(e.to_string())) + // Use platform_value conversion which handles BigInt for balance/revision + // and outputs id as Uint8Array, publicKeys as plain objects + let value = self.0.to_object()?; + serialization::platform_value_to_object(&value) } #[wasm_bindgen(js_name = "toJSON")] pub fn to_json(&self) -> WasmDppResult { - self.to_object() + serialization::to_json(&self.0) + } + + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + serialization::from_json(js_value).map(IdentityWasm) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + let value = serialization::js_value_to_platform_value(&js_value)?; + let identity = Identity::try_from_platform_versioned(value, PlatformVersion::latest())?; + Ok(IdentityWasm(identity)) } #[wasm_bindgen(js_name = "fromBytes")] @@ -185,9 +169,3 @@ impl IdentityWasm { Ok(IdentityWasm(identity)) } } - -impl IdentityWasm { - pub fn get_rs_public_keys(&self) -> BTreeMap { - self.0.public_keys().clone() - } -} diff --git a/packages/wasm-dpp2/src/identity/partial_identity.rs b/packages/wasm-dpp2/src/identity/partial_identity.rs index 6a3449ec75e..2420f173c1e 100644 --- a/packages/wasm-dpp2/src/identity/partial_identity.rs +++ b/packages/wasm-dpp2/src/identity/partial_identity.rs @@ -128,6 +128,89 @@ impl PartialIdentityWasm { Ok(()) } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> WasmDppResult { + let obj = Object::new(); + + Reflect::set(&obj, &"id".into(), &self.id().to_base58().into()) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + // Serialize loaded public keys as object with string keys and JSON values + let loaded_keys_obj = Object::new(); + for (k, v) in self.0.loaded_public_keys.clone() { + let key_wasm = IdentityPublicKeyWasm::from(v); + Reflect::set( + &loaded_keys_obj, + &k.to_string().into(), + &key_wasm.to_json()?, + ) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + } + Reflect::set(&obj, &"loadedPublicKeys".into(), &loaded_keys_obj.into()) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + match self.0.balance { + Some(b) => Reflect::set(&obj, &"balance".into(), &JsValue::from(b as f64)) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + None => Reflect::set(&obj, &"balance".into(), &JsValue::NULL) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + }; + + match self.0.revision { + Some(r) => Reflect::set(&obj, &"revision".into(), &JsValue::from(r as f64)) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + None => Reflect::set(&obj, &"revision".into(), &JsValue::NULL) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + }; + + let not_found_arr = Array::new(); + for k in self.0.not_found_public_keys.iter() { + not_found_arr.push(&JsValue::from(*k)); + } + Reflect::set(&obj, &"notFoundPublicKeys".into(), ¬_found_arr.into()) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + Ok(obj.into()) + } + + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> WasmDppResult { + let obj = Object::new(); + + Reflect::set(&obj, &"id".into(), &JsValue::from(self.id())) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + Reflect::set( + &obj, + &"loadedPublicKeys".into(), + &self.loaded_public_keys()?.into(), + ) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + match self.0.balance { + Some(b) => Reflect::set(&obj, &"balance".into(), &js_sys::BigInt::from(b).into()) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + None => Reflect::set(&obj, &"balance".into(), &JsValue::NULL) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + }; + + match self.0.revision { + Some(r) => Reflect::set(&obj, &"revision".into(), &js_sys::BigInt::from(r).into()) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + None => Reflect::set(&obj, &"revision".into(), &JsValue::NULL) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?, + }; + + Reflect::set( + &obj, + &"notFoundPublicKeys".into(), + &self.not_found_public_keys().into(), + ) + .map_err(|e| WasmDppError::serialization(format!("{:?}", e)))?; + + Ok(obj.into()) + } } pub fn js_value_to_loaded_public_keys( diff --git a/packages/wasm-dpp2/src/identity/public_key.rs b/packages/wasm-dpp2/src/identity/public_key.rs index cced3ab35fe..bb780286649 100644 --- a/packages/wasm-dpp2/src/identity/public_key.rs +++ b/packages/wasm-dpp2/src/identity/public_key.rs @@ -4,6 +4,7 @@ use crate::enums::keys::purpose::PurposeWasm; use crate::enums::keys::security_level::SecurityLevelWasm; use crate::enums::network::NetworkWasm; use crate::error::{WasmDppError, WasmDppResult}; +use crate::serialization; use crate::utils::IntoWasm; use dpp::dashcore::Network; use dpp::dashcore::secp256k1::hashes::hex::{Case, DisplayHex}; @@ -12,12 +13,17 @@ use dpp::identity::hash::IdentityPublicKeyHashMethodsV0; use dpp::identity::identity_public_key::accessors::v0::{ IdentityPublicKeyGettersV0, IdentityPublicKeySettersV0, }; +use dpp::identity::identity_public_key::conversion::json::IdentityPublicKeyJsonConversionMethodsV0; +use dpp::identity::identity_public_key::conversion::platform_value::IdentityPublicKeyPlatformValueConversionMethodsV0; use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel, TimestampMillis}; use dpp::platform_value::BinaryData; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; use dpp::platform_value::string_encoding::{decode, encode}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::version::PlatformVersion; +use serde_json::Value as JsonValue; +use serde_wasm_bindgen::from_value as serde_from_value; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -97,13 +103,13 @@ impl IdentityPublicKeyWasm { pub fn validate_private_key( &self, js_private_key_bytes: Vec, - js_network: JsValue, + #[wasm_bindgen(unchecked_param_type = "Network | string")] network: JsValue, ) -> WasmDppResult { let mut private_key_bytes = [0u8; 32]; let len = js_private_key_bytes.len().min(32); private_key_bytes[..len].copy_from_slice(&js_private_key_bytes[..len]); - let network = Network::from(NetworkWasm::try_from(js_network)?); + let network = Network::from(NetworkWasm::try_from(network)?); let is_valid = self .0 @@ -288,4 +294,56 @@ impl IdentityPublicKeyWasm { Ok(IdentityPublicKeyWasm(public_key)) } + + /// Serialize to JS object (non-human-readable). + /// + /// Uses platform_value conversion which properly handles the tagged enum + /// and removes None fields like disabledAt. + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> WasmDppResult { + let value = self.0.to_cleaned_object().map_err(WasmDppError::from)?; + serialization::platform_value_to_object(&value) + } + + /// Deserialize from JS object (non-human-readable). + /// + /// Uses platform_value conversion which properly handles the tagged enum. + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + let platform_value = serialization::platform_value_from_object(js_value)?; + let platform_version = PlatformVersion::latest(); + let key = IdentityPublicKey::from_object(platform_value, platform_version) + .map_err(WasmDppError::from)?; + Ok(IdentityPublicKeyWasm(key)) + } + + /// Serialize to JSON-compatible JS object (human-readable). + /// + /// Uses serde_json conversion which properly handles the tagged enum + /// and serializes binary data as base64 strings. + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> WasmDppResult { + let json_value = self.0.to_json_object().map_err(WasmDppError::from)?; + serialization::to_json(&json_value) + } + + /// Deserialize from JSON-compatible JS object (human-readable). + /// + /// Uses serde_json conversion which properly handles the tagged enum + /// and deserializes base64 strings to binary data. + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + let json_value: JsonValue = serde_from_value(js_value).map_err(|err| { + WasmDppError::serialization(format!( + "IdentityPublicKey.fromJSON: unable to parse JSON: {}", + err + )) + })?; + + let platform_version = PlatformVersion::latest(); + let key = IdentityPublicKey::from_json_object(json_value, platform_version) + .map_err(WasmDppError::from)?; + + Ok(IdentityPublicKeyWasm(key)) + } } diff --git a/packages/wasm-dpp2/src/identity/transitions/create_transition.rs b/packages/wasm-dpp2/src/identity/transitions/create_transition.rs index 8bcfc7e0a53..98dde450bc4 100644 --- a/packages/wasm-dpp2/src/identity/transitions/create_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/create_transition.rs @@ -3,11 +3,12 @@ use crate::enums::platform::PlatformVersionWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; use crate::identity::transitions::public_key_in_creation::IdentityPublicKeyInCreationWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::identity::state_transition::AssetLockProved; use dpp::platform_value::BinaryData; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; -use dpp::platform_value::string_encoding::decode; +use dpp::platform_value::string_encoding::{decode, encode}; use dpp::prelude::UserFeeIncrease; use dpp::serialization::{PlatformDeserializable, PlatformSerializable, Signable}; use dpp::state_transition::identity_create_transition::IdentityCreateTransition; @@ -169,6 +170,16 @@ impl IdentityCreateTransitionWasm { Ok(()) } + #[wasm_bindgen(js_name = "toHex")] + pub fn to_hex(&self) -> WasmDppResult { + Ok(encode(self.to_bytes()?.as_slice(), Hex)) + } + + #[wasm_bindgen(js_name = "toBase64")] + pub fn to_base64(&self) -> WasmDppResult { + Ok(encode(self.to_bytes()?.as_slice(), Base64)) + } + #[wasm_bindgen(js_name = "toStateTransition")] pub fn to_state_transition(&self) -> StateTransitionWasm { StateTransitionWasm::from(StateTransition::IdentityCreate(self.clone().0)) @@ -188,3 +199,5 @@ impl IdentityCreateTransitionWasm { } } } + +impl_wasm_conversions!(IdentityCreateTransitionWasm, IdentityCreateTransition); diff --git a/packages/wasm-dpp2/src/identity/transitions/credit_withdrawal_transition.rs b/packages/wasm-dpp2/src/identity/transitions/credit_withdrawal_transition.rs index 4b6fc146e01..006de993ca8 100644 --- a/packages/wasm-dpp2/src/identity/transitions/credit_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/credit_withdrawal_transition.rs @@ -4,6 +4,7 @@ use crate::enums::keys::purpose::PurposeWasm; use crate::enums::withdrawal::PoolingWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use crate::utils::IntoWasm; use dpp::identity::KeyID; @@ -242,7 +243,7 @@ impl IdentityCreditWithdrawalTransitionWasm { Ok(encode(bytes.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; Ok(encode(bytes.as_slice(), Base64)) @@ -277,3 +278,8 @@ impl IdentityCreditWithdrawalTransitionWasm { } } } + +impl_wasm_conversions!( + IdentityCreditWithdrawalTransitionWasm, + IdentityCreditWithdrawalTransition +); diff --git a/packages/wasm-dpp2/src/identity/transitions/identity_credit_transfer_transition.rs b/packages/wasm-dpp2/src/identity/transitions/identity_credit_transfer_transition.rs index 156b3fd84b5..40e153e0463 100644 --- a/packages/wasm-dpp2/src/identity/transitions/identity_credit_transfer_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/identity_credit_transfer_transition.rs @@ -1,5 +1,6 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::platform_value::BinaryData; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; @@ -69,7 +70,7 @@ impl IdentityCreditTransferWasm { Ok(encode(bytes.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; Ok(encode(bytes.as_slice(), Base64)) @@ -211,3 +212,5 @@ impl IdentityCreditTransferWasm { self.0.set_signature(data) } } + +impl_wasm_conversions!(IdentityCreditTransferWasm, IdentityCreditTransferTransition); diff --git a/packages/wasm-dpp2/src/identity/transitions/masternode_vote_transition.rs b/packages/wasm-dpp2/src/identity/transitions/masternode_vote_transition.rs index 84c44fa0ded..07ca5dfb8b9 100644 --- a/packages/wasm-dpp2/src/identity/transitions/masternode_vote_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/masternode_vote_transition.rs @@ -2,12 +2,13 @@ use crate::VoteWasm; use crate::asset_lock_proof::AssetLockProofWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::identity::KeyID; use dpp::identity::state_transition::OptionallyAssetLockProved; use dpp::platform_value::BinaryData; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; -use dpp::platform_value::string_encoding::decode; +use dpp::platform_value::string_encoding::{decode, encode}; use dpp::prelude::IdentityNonce; use dpp::serialization::{PlatformDeserializable, PlatformSerializable, Signable}; use dpp::state_transition::masternode_vote_transition::MasternodeVoteTransition; @@ -233,4 +234,18 @@ impl MasternodeVoteTransitionWasm { )), } } + + #[wasm_bindgen(js_name = "toHex")] + pub fn to_hex(&self) -> WasmDppResult { + let bytes = self.0.serialize_to_bytes()?; + Ok(encode(bytes.as_slice(), Hex)) + } + + #[wasm_bindgen(js_name = "toBase64")] + pub fn to_base64(&self) -> WasmDppResult { + let bytes = self.0.serialize_to_bytes()?; + Ok(encode(bytes.as_slice(), Base64)) + } } + +impl_wasm_conversions!(MasternodeVoteTransitionWasm, MasternodeVoteTransition); diff --git a/packages/wasm-dpp2/src/identity/transitions/public_key_in_creation.rs b/packages/wasm-dpp2/src/identity/transitions/public_key_in_creation.rs index 7f68f31a36e..f79012c6b56 100644 --- a/packages/wasm-dpp2/src/identity/transitions/public_key_in_creation.rs +++ b/packages/wasm-dpp2/src/identity/transitions/public_key_in_creation.rs @@ -4,6 +4,7 @@ use crate::enums::keys::purpose::PurposeWasm; use crate::enums::keys::security_level::SecurityLevelWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identity::public_key::IdentityPublicKeyWasm; +use crate::impl_wasm_conversions; use crate::utils::IntoWasm; use dpp::identity::contract_bounds::ContractBounds; use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; @@ -248,3 +249,5 @@ impl IdentityPublicKeyInCreationWasm { Ok(add_public_keys) } } + +impl_wasm_conversions!(IdentityPublicKeyInCreationWasm, IdentityPublicKeyInCreation); diff --git a/packages/wasm-dpp2/src/identity/transitions/top_up_transition.rs b/packages/wasm-dpp2/src/identity/transitions/top_up_transition.rs index 7900955d4c9..0f37b398b75 100644 --- a/packages/wasm-dpp2/src/identity/transitions/top_up_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/top_up_transition.rs @@ -1,6 +1,7 @@ use crate::asset_lock_proof::AssetLockProofWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::identifier::Identifier; use dpp::identity::state_transition::{AssetLockProved, OptionallyAssetLockProved}; @@ -135,7 +136,7 @@ impl IdentityTopUpTransitionWasm { Ok(encode(bytes.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; Ok(encode(bytes.as_slice(), Base64)) @@ -181,3 +182,5 @@ impl IdentityTopUpTransitionWasm { } } } + +impl_wasm_conversions!(IdentityTopUpTransitionWasm, IdentityTopUpTransition); diff --git a/packages/wasm-dpp2/src/identity/transitions/update_transition.rs b/packages/wasm-dpp2/src/identity/transitions/update_transition.rs index 32e9d3d00ac..a1e423c14fa 100644 --- a/packages/wasm-dpp2/src/identity/transitions/update_transition.rs +++ b/packages/wasm-dpp2/src/identity/transitions/update_transition.rs @@ -3,6 +3,7 @@ use crate::enums::keys::purpose::PurposeWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; use crate::identity::transitions::public_key_in_creation::IdentityPublicKeyInCreationWasm; +use crate::impl_wasm_conversions; use crate::state_transitions::StateTransitionWasm; use dpp::identity::KeyID; use dpp::identity::state_transition::OptionallyAssetLockProved; @@ -229,7 +230,7 @@ impl IdentityUpdateTransitionWasm { Ok(encode(bytes.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; Ok(encode(bytes.as_slice(), Base64)) @@ -261,3 +262,5 @@ impl IdentityUpdateTransitionWasm { } } } + +impl_wasm_conversions!(IdentityUpdateTransitionWasm, IdentityUpdateTransition); diff --git a/packages/wasm-dpp2/src/lib.rs b/packages/wasm-dpp2/src/lib.rs index 9610a67545a..68b6342dd8c 100644 --- a/packages/wasm-dpp2/src/lib.rs +++ b/packages/wasm-dpp2/src/lib.rs @@ -23,6 +23,7 @@ pub mod identity; pub mod mock_bls; pub mod private_key; pub mod public_key; +pub mod serialization; pub mod state_transitions; pub mod tokens; pub mod utils; @@ -44,5 +45,5 @@ pub use state_transitions::base::{GroupStateTransitionInfoWasm, StateTransitionW pub use tokens::*; pub use voting::{ ContenderWithSerializedDocumentWasm, ContestedDocumentVotePollWinnerInfoWasm, - ResourceVoteChoiceWasm, VotePollWasm, VoteWasm, + ResourceVoteChoiceWasm, ResourceVoteWasm, VotePollWasm, VoteWasm, }; diff --git a/packages/wasm-dpp2/src/private_key.rs b/packages/wasm-dpp2/src/private_key.rs index f1368448460..e9172ee7d76 100644 --- a/packages/wasm-dpp2/src/private_key.rs +++ b/packages/wasm-dpp2/src/private_key.rs @@ -34,22 +34,28 @@ impl PrivateKeyWasm { } #[wasm_bindgen(js_name = "fromBytes")] - pub fn from_bytes(bytes: Vec, js_network: JsValue) -> WasmDppResult { - let network = NetworkWasm::try_from(js_network)?; + pub fn from_bytes( + bytes: Vec, + #[wasm_bindgen(unchecked_param_type = "Network | string")] network: JsValue, + ) -> WasmDppResult { + let network_wasm = NetworkWasm::try_from(network)?; let key_bytes: [u8; 32] = bytes.try_into().map_err(|_| { WasmDppError::invalid_argument("Private key bytes must be exactly 32 bytes".to_string()) })?; - let pk = PrivateKey::from_byte_array(&key_bytes, network.into()) + let pk = PrivateKey::from_byte_array(&key_bytes, network_wasm.into()) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; Ok(PrivateKeyWasm(pk)) } #[wasm_bindgen(js_name = "fromHex")] - pub fn from_hex(hex_key: &str, js_network: JsValue) -> WasmDppResult { - let network = NetworkWasm::try_from(js_network)?; + pub fn from_hex( + hex_key: &str, + #[wasm_bindgen(unchecked_param_type = "Network | string")] network: JsValue, + ) -> WasmDppResult { + let network_wasm = NetworkWasm::try_from(network)?; let bytes = Vec::from_hex(hex_key) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; @@ -58,7 +64,7 @@ impl PrivateKeyWasm { WasmDppError::invalid_argument("Private key hex must decode to 32 bytes".to_string()) })?; - let pk = PrivateKey::from_byte_array(&key_bytes, network.into()) + let pk = PrivateKey::from_byte_array(&key_bytes, network_wasm.into()) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; Ok(PrivateKeyWasm(pk)) diff --git a/packages/wasm-dpp2/src/serialization/bytes_b64.rs b/packages/wasm-dpp2/src/serialization/bytes_b64.rs new file mode 100644 index 00000000000..b90ebe79966 --- /dev/null +++ b/packages/wasm-dpp2/src/serialization/bytes_b64.rs @@ -0,0 +1,116 @@ +use dpp::platform_value::string_encoding::{Encoding, decode, encode}; +use serde::de::{self, Visitor}; +use serde::{Deserialize, Deserializer, Serializer}; +use std::fmt; + +pub fn serialize(bytes: &[u8], serializer: S) -> Result +where + S: Serializer, +{ + if serializer.is_human_readable() { + // JSON, YAML, etc. → Base64 string + let s = encode(bytes, Encoding::Base64); + serializer.serialize_str(&s) + } else { + // Binary / wasm / serde_wasm_bindgen → real bytes + serializer.serialize_bytes(bytes) + } +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + if deserializer.is_human_readable() { + // Expect Base64 string in JSON + let s = String::deserialize(deserializer)?; + decode(&s, Encoding::Base64).map_err(de::Error::custom) + } else { + // Expect bytes for binary formats / serde_wasm_bindgen + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + Ok(v.to_vec()) + } + } + + deserializer.deserialize_bytes(BytesVisitor) + } +} + +/// Generic serde helper for `Option<[u8; N]>` as base64 +pub mod option { + use super::*; + + pub fn serialize( + value: &Option<[u8; N]>, + serializer: S, + ) -> Result + where + S: Serializer, + { + match value { + Some(bytes) => super::serialize(bytes.as_slice(), serializer), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct OptionVisitor; + + impl<'de, const N: usize> Visitor<'de> for OptionVisitor { + type Value = Option<[u8; N]>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "optional {} bytes", N) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = super::deserialize(deserializer)?; + if bytes.len() == N { + let mut arr = [0u8; N]; + arr.copy_from_slice(&bytes); + Ok(Some(arr)) + } else { + Err(de::Error::custom(format!( + "expected {} bytes, got {}", + N, + bytes.len() + ))) + } + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(OptionVisitor::) + } +} diff --git a/packages/wasm-dpp2/src/serialization/conversions.rs b/packages/wasm-dpp2/src/serialization/conversions.rs new file mode 100644 index 00000000000..180af2d7fcf --- /dev/null +++ b/packages/wasm-dpp2/src/serialization/conversions.rs @@ -0,0 +1,401 @@ +//! Format-aware serialization helpers for WASM. +//! +//! This module provides serialization/deserialization helpers that use serde's +//! `is_human_readable()` mechanism to determine output format: +//! - Human-readable (JSON): identifiers as Base58 strings, bytes as base64 +//! - Non-human-readable (binary/WASM): identifiers as bytes → Uint8Array +//! +//! ## Main API +//! +//! For WASM types implementing `Serialize`/`Deserialize`: +//! - [`to_json`] / [`from_json`] - Human-readable format (base58 identifiers, base64 bytes) +//! - [`to_object`] / [`from_object`] - Binary format (Uint8Array for bytes, BigInt for u64) +//! +//! For converting between JsValue and Rust types: +//! - [`js_value_to_json`] - JsValue → serde_json::Value (handles BigInt, WASM objects) +//! - [`json_to_js_value`] - serde_json::Value → JsValue +//! - [`js_value_to_platform_value`] - JsValue → platform_value::Value +//! +//! For platform_value specifically: +//! - [`platform_value_to_object`] / [`platform_value_from_object`] - Binary format +//! - [`platform_value_to_json`] - Human-readable format + +use crate::error::{WasmDppError, WasmDppResult}; +use dpp::platform_value; +use js_sys::Object; +use serde::Serialize; +use serde::de::DeserializeOwned; +use serde_json::Value as JsonValue; +use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; + +/// Convert JsValue to serde_json::Value, handling BigInt values and WASM objects. +/// +/// This function: +/// - Converts BigInt values to strings (JSON doesn't support BigInt natively) +/// - For WASM objects with a `toJSON` method, calls that method first to get proper JSON +/// - Falls back to serde_wasm_bindgen conversion for plain objects +pub fn js_value_to_json(value: &JsValue) -> WasmDppResult { + // Check if the value has a toJSON method (WASM objects like DataContractWasm, IdentityWasm) + if value.is_object() + && !value.is_null() + && !js_sys::Array::is_array(value) + && let Ok(to_json_fn) = js_sys::Reflect::get(value, &JsValue::from_str("toJSON")) + && to_json_fn.is_function() + { + let func: js_sys::Function = to_json_fn.unchecked_into(); + // Call toJSON() on the object + if let Ok(json_result) = func.call0(value) { + // Recursively convert the result (it might contain BigInt or nested WASM objects) + return js_value_to_json(&json_result); + } + } + + let normalized = normalize_js_value_for_json(value)?; + serde_wasm_bindgen::from_value(normalized).map_err(|e| { + WasmDppError::serialization(format!("Failed to convert JsValue to JSON: {}", e)) + }) +} + +/// Convert serde_json::Value to JsValue using JSON-compatible serialization. +/// +/// This ensures objects become plain JS objects (not Maps). +pub fn json_to_js_value(value: &JsonValue) -> WasmDppResult { + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + value.serialize(&serializer).map_err(|e| { + WasmDppError::serialization(format!("Failed to convert JSON to JsValue: {}", e)) + }) +} + +/// Recursively normalizes a JsValue for JSON conversion. +/// +/// This converts: +/// - BigInt values to strings (JSON doesn't support BigInt natively) +/// - Uint8Array to plain arrays (so they serialize as JSON number arrays) +/// +/// Performance: Uses fast path for primitives, only recursively processes objects/arrays. +fn normalize_js_value_for_json(value: &JsValue) -> WasmDppResult { + // Fast path: primitives that can't contain BigInt + if value.is_string() + || value.as_f64().is_some() + || value.is_null() + || value.is_undefined() + || value.as_bool().is_some() + { + return Ok(value.clone()); + } + + if value.is_bigint() { + let bigint: js_sys::BigInt = value.clone().unchecked_into(); + let bigint_str = bigint + .to_string(10) + .map(|s| s.into()) + .unwrap_or_else(|_| "0".to_string()); + return Ok(JsValue::from_str(&bigint_str)); + } + + // Convert Uint8Array to plain array for JSON compatibility + if value.is_instance_of::() { + let uint8_array: js_sys::Uint8Array = value.clone().unchecked_into(); + let plain_array = js_sys::Array::from(&uint8_array); + return Ok(plain_array.into()); + } + + if js_sys::Array::is_array(value) { + let arr = js_sys::Array::from(value); + let new_arr = js_sys::Array::new(); + for i in 0..arr.length() { + let elem = arr.get(i); + let normalized = normalize_js_value_for_json(&elem)?; + new_arr.push(&normalized); + } + return Ok(new_arr.into()); + } + + if value.is_object() && !value.is_null() { + let obj = Object::from(value.clone()); + let new_obj = Object::new(); + let keys = Object::keys(&obj); + for i in 0..keys.length() { + let key = keys.get(i); + if key.as_string().is_some() { + let prop_value = js_sys::Reflect::get(value, &key).map_err(|e| { + WasmDppError::serialization(format!("Failed to get property: {:?}", e)) + })?; + let normalized = normalize_js_value_for_json(&prop_value)?; + js_sys::Reflect::set(&new_obj, &key, &normalized).map_err(|e| { + WasmDppError::serialization(format!("Failed to set property: {:?}", e)) + })?; + } + } + return Ok(new_obj.into()); + } + + // Anything else (Symbol, Function, etc.): pass through + Ok(value.clone()) +} + +/// Serialize to JsValue as a JS object (non-human-readable). +/// +/// Uses the serde-wasm-bindgen serializer with `is_human_readable() -> false`, +/// so types like OutPoint serialize as bytes (Uint8Array). +/// Uses `serialize_large_number_types_as_bigints(true)` for u64/i64 -> BigInt. +pub fn to_object(value: &T) -> WasmDppResult { + let serializer = serde_wasm_bindgen::Serializer::new() + .serialize_maps_as_objects(true) + .serialize_bytes_as_arrays(false) + .serialize_large_number_types_as_bigints(true); + value + .serialize(&serializer) + .map_err(|e| WasmDppError::serialization(format!("toObject: {}", e))) +} + +/// Deserialize from JsValue (non-human-readable). +/// +/// Uses the serde-wasm-bindgen deserializer with `is_human_readable() -> false`, +/// so types like OutPoint expect bytes (Uint8Array). +pub fn from_object(value: JsValue) -> WasmDppResult { + serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(format!("fromObject: {}", e))) +} + +/// Serialize to JsValue as JSON-compatible (human-readable). +/// +/// Uses `serialize_human_readable(true)` so types like Identifier serialize as base58 strings, +/// BinaryData as base64 strings, etc. +pub fn to_json(value: &T) -> WasmDppResult { + let serializer = + serde_wasm_bindgen::Serializer::json_compatible().serialize_human_readable(true); + value + .serialize(&serializer) + .map_err(|e| WasmDppError::serialization(format!("toJSON: {}", e))) +} + +/// Deserialize from JsValue (human-readable JSON). +/// +/// Uses the human-readable deserializer with `is_human_readable() -> true`, +/// so types like BinaryData expect base64 strings. +pub fn from_json(value: JsValue) -> WasmDppResult { + serde_wasm_bindgen::from_value_json(value) + .map_err(|e| WasmDppError::serialization(format!("fromJSON: {}", e))) +} + +/// Serialize platform_value::Value to JsValue as a JS object (non-human-readable). +/// +/// Uses serialize_maps_as_objects(true) to ensure objects are plain JS objects. +/// Uses `serialize_bytes_as_arrays(false)` so bytes become Uint8Array (expected by JS API). +/// Uses `serialize_large_number_types_as_bigints(true)` for u64/i64 -> BigInt. +pub fn platform_value_to_object(value: &platform_value::Value) -> WasmDppResult { + let serializer = serde_wasm_bindgen::Serializer::new() + .serialize_maps_as_objects(true) + .serialize_bytes_as_arrays(false) + .serialize_large_number_types_as_bigints(true); + value + .serialize(&serializer) + .map_err(|e| WasmDppError::serialization(format!("platform_value_to_object: {}", e))) +} + +/// Serialize platform_value::Value to JsValue as JSON-compatible (human-readable). +/// +/// Converts Value::Identifier and Value::Bytes to base58/base64 strings for JSON compatibility. +pub fn platform_value_to_json(value: &platform_value::Value) -> WasmDppResult { + let converted = convert_value_for_json(value); + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + converted + .serialize(&serializer) + .map_err(|e| WasmDppError::serialization(format!("platform_value_to_json: {}", e))) +} + +/// Convert platform_value::Value for JSON serialization. +/// Transforms binary types to their string representations. +fn convert_value_for_json(value: &platform_value::Value) -> platform_value::Value { + use dpp::platform_value::string_encoding::{Encoding, encode}; + + match value { + platform_value::Value::Identifier(bytes) => { + platform_value::Value::Text(encode(bytes, Encoding::Base58)) + } + platform_value::Value::Bytes(bytes) => { + platform_value::Value::Text(encode(bytes, Encoding::Base64)) + } + platform_value::Value::Bytes20(bytes) => { + platform_value::Value::Text(encode(bytes, Encoding::Base64)) + } + platform_value::Value::Bytes32(bytes) => { + platform_value::Value::Text(encode(bytes, Encoding::Base64)) + } + platform_value::Value::Bytes36(bytes) => { + platform_value::Value::Text(encode(bytes, Encoding::Base64)) + } + platform_value::Value::Map(map) => platform_value::Value::Map( + map.iter() + .map(|(k, v)| (convert_value_for_json(k), convert_value_for_json(v))) + .collect(), + ), + platform_value::Value::Array(arr) => { + platform_value::Value::Array(arr.iter().map(convert_value_for_json).collect()) + } + other => other.clone(), + } +} + +/// Deserialize JsValue to platform_value::Value. +/// +/// serde-wasm-bindgen's deserialize_any handles Uint8Array via visit_byte_buf, which creates +/// Value::Bytes. BigInt is handled via visit_i64/visit_u64. +pub fn platform_value_from_object(value: JsValue) -> WasmDppResult { + serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(format!("platform_value_from_object: {}", e))) +} + +/// Convert a JsValue to platform_value::Value, properly handling BigInt and Uint8Array. +pub fn js_value_to_platform_value(value: &JsValue) -> WasmDppResult { + // Null + if value.is_null() || value.is_undefined() { + return Ok(platform_value::Value::Null); + } + + // Boolean + if let Some(b) = value.as_bool() { + return Ok(platform_value::Value::Bool(b)); + } + + // Number (f64) - try to convert to integer if it's a whole number + if let Some(num) = value.as_f64() { + // Check if it's a whole number that fits in i64/u64 + if num.fract() == 0.0 { + if num >= 0.0 && num <= u64::MAX as f64 { + return Ok(platform_value::Value::U64(num as u64)); + } else if num >= i64::MIN as f64 && num < 0.0 { + return Ok(platform_value::Value::I64(num as i64)); + } + } + return Ok(platform_value::Value::Float(num)); + } + + // BigInt - convert to appropriate integer type + if value.is_bigint() { + let bigint: js_sys::BigInt = value.clone().unchecked_into(); + // Get string representation and parse + let bigint_str: String = bigint + .to_string(10) + .map(|s| s.into()) + .unwrap_or_else(|_| "0".to_string()); + + // Try parsing as u64 first (most common for revision, timestamps, etc.) + if let Ok(val) = bigint_str.parse::() { + return Ok(platform_value::Value::U64(val)); + } + // Try i64 for negative values + if let Ok(val) = bigint_str.parse::() { + return Ok(platform_value::Value::I64(val)); + } + // Try u128/i128 for larger values + if let Ok(val) = bigint_str.parse::() { + return Ok(platform_value::Value::U128(val)); + } + if let Ok(val) = bigint_str.parse::() { + return Ok(platform_value::Value::I128(val)); + } + // Fall back to string if it doesn't fit + return Ok(platform_value::Value::Text(bigint_str)); + } + + // String + if let Some(s) = value.as_string() { + return Ok(platform_value::Value::Text(s)); + } + + // Uint8Array - convert to bytes + if value.is_instance_of::() { + let uint8_array: js_sys::Uint8Array = value.clone().unchecked_into(); + let bytes = uint8_array.to_vec(); + // Check for identifier (32 bytes) + if bytes.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + return Ok(platform_value::Value::Identifier(arr)); + } + return Ok(platform_value::Value::Bytes(bytes)); + } + + // Array + if js_sys::Array::is_array(value) { + let arr = js_sys::Array::from(value); + let mut result = Vec::new(); + for i in 0..arr.length() { + let elem = arr.get(i); + result.push(js_value_to_platform_value(&elem)?); + } + return Ok(platform_value::Value::Array(result)); + } + + // Object (Map) + if value.is_object() { + let obj = js_sys::Object::from(value.clone()); + let keys = js_sys::Object::keys(&obj); + let mut map = Vec::new(); + for i in 0..keys.length() { + let key = keys.get(i); + if let Some(key_str) = key.as_string() { + let prop_value = js_sys::Reflect::get(value, &key).map_err(|e| { + WasmDppError::serialization(format!("Failed to get property: {:?}", e)) + })?; + let converted = js_value_to_platform_value(&prop_value)?; + map.push((platform_value::Value::Text(key_str), converted)); + } + } + return Ok(platform_value::Value::Map(map)); + } + + // Fallback + Err(WasmDppError::serialization( + "Unsupported JsValue type for platform_value conversion".to_string(), + )) +} + +/// Macro to implement `toObject`, `fromObject`, `toJSON`, and `fromJSON` methods +/// for a wasm_bindgen newtype wrapper using the serialization::conversions module. +/// +/// # Usage +/// +/// ```ignore +/// // For newtype wrappers: WrapperType(InnerType) +/// // JS class name defaults to Rust type name without "Wasm" suffix +/// impl_wasm_conversions!(MyTypeWasm, MyType); +/// ``` +/// +/// The inner type must implement `Serialize` and `DeserializeOwned`. +/// The wrapper type must implement `From` and have a `.0` field. +#[macro_export] +macro_rules! impl_wasm_conversions { + // Two-argument form: wrapper type and JS class name + ($wrapper:ty, $js_class:ident) => { + #[wasm_bindgen::prelude::wasm_bindgen(js_class = $js_class)] + impl $wrapper { + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> Result { + $crate::serialization::conversions::to_object(&self.0) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromObject)] + pub fn from_object( + obj: wasm_bindgen::JsValue, + ) -> Result<$wrapper, $crate::error::WasmDppError> { + $crate::serialization::conversions::from_object(obj).map(Self) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + $crate::serialization::conversions::to_json(&self.0) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromJSON)] + pub fn from_json( + js: wasm_bindgen::JsValue, + ) -> Result<$wrapper, $crate::error::WasmDppError> { + $crate::serialization::conversions::from_json(js).map(Self) + } + } + }; +} diff --git a/packages/wasm-dpp2/src/serialization/mod.rs b/packages/wasm-dpp2/src/serialization/mod.rs new file mode 100644 index 00000000000..714803fccae --- /dev/null +++ b/packages/wasm-dpp2/src/serialization/mod.rs @@ -0,0 +1,11 @@ +//! Serialization utilities for WASM bindings. +//! +//! This module contains: +//! - `bytes_b64`: Serde helpers for bytes that serialize as Base64 in human-readable formats +//! - `conversions`: Format-aware conversion helpers between Rust/JS/JSON representations + +pub mod bytes_b64; +pub mod conversions; + +// Re-export commonly used items from conversions +pub use conversions::*; diff --git a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs index c2a563c0a84..8c75a71a8ce 100644 --- a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs @@ -236,7 +236,7 @@ impl StateTransitionWasm { Ok(JsValue::from(encode(bytes.as_slice(), Encoding::Hex))) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { let bytes = self.0.serialize_to_bytes()?; diff --git a/packages/wasm-dpp2/src/state_transitions/batch/batch_transition.rs b/packages/wasm-dpp2/src/state_transitions/batch/batch_transition.rs index 205f7930130..5496004bc46 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/batch_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/batch_transition.rs @@ -1,24 +1,20 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::serialization; use crate::state_transitions::StateTransitionWasm; use crate::state_transitions::batch::batched_transition::BatchedTransitionWasm; -use crate::state_transitions::batch::document_transition::DocumentTransitionWasm; use crate::utils::IntoWasm; use dpp::fee::Credits; use dpp::identity::KeyID; use dpp::platform_value::BinaryData; use dpp::platform_value::string_encoding::Encoding::{Base64, Hex}; use dpp::platform_value::string_encoding::{decode, encode}; -use dpp::prelude::Identifier; use dpp::prelude::{IdentityNonce, UserFeeIncrease}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0; use dpp::state_transition::batch_transition::batched_transition::BatchedTransition; -use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; -use dpp::state_transition::batch_transition::{ - BatchTransition, BatchTransitionV0, BatchTransitionV1, -}; +use dpp::state_transition::batch_transition::{BatchTransition, BatchTransitionV1}; use dpp::state_transition::{ StateTransition, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionOwned, StateTransitionSingleSigned, @@ -69,14 +65,12 @@ impl BatchTransitionWasm { "BatchTransition".to_string() } - #[wasm_bindgen(js_name = "fromV1BatchedTransitions")] - pub fn from_v1_batched_transitions( + #[wasm_bindgen(js_name = "fromBatchedTransitions")] + pub fn from_batched_transitions( js_batched_transitions: &js_sys::Array, #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] owner_id: &JsValue, user_fee_increase: UserFeeIncrease, - signature_public_key_id: Option, - signature: Option>, ) -> WasmDppResult { let transitions = convert_array_to_vec_batched(js_batched_transitions)?; @@ -85,47 +79,14 @@ impl BatchTransitionWasm { owner_id: IdentifierWasm::try_from(owner_id)?.into(), transitions, user_fee_increase, - signature_public_key_id: signature_public_key_id.unwrap_or(0u32), - signature: BinaryData::from(signature.unwrap_or_default()), - }, - ))) - } - - #[wasm_bindgen(js_name = "fromV0Transitions")] - pub fn from_v0_transitions( - document_transitions: &js_sys::Array, - #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] - js_owner_id: &JsValue, - user_fee_increase: Option, - signature_public_key_id: Option, - signature: Option>, - ) -> WasmDppResult { - let owner_id: Identifier = IdentifierWasm::try_from(js_owner_id)?.into(); - - let mut transitions: Vec = - Vec::with_capacity(document_transitions.length() as usize); - - for js_document_transition in document_transitions.iter() { - let document_transition: DocumentTransitionWasm = js_document_transition - .to_wasm::("DocumentTransition")? - .clone(); - - transitions.push(DocumentTransition::from(document_transition)); - } - - Ok(BatchTransitionWasm(BatchTransition::V0( - BatchTransitionV0 { - owner_id, - transitions, - user_fee_increase: user_fee_increase.unwrap_or(0), - signature_public_key_id: signature_public_key_id.unwrap_or(0), - signature: BinaryData::from(signature.unwrap_or_default()), + signature_public_key_id: 0u32, + signature: BinaryData::default(), }, ))) } #[wasm_bindgen(getter = "transitions")] - pub fn get_transitions(&self) -> Vec { + pub fn get_batched_transitions(&self) -> Vec { self.0 .transitions_iter() .map(|transition| BatchedTransitionWasm::from(transition.to_owned_transition())) @@ -219,12 +180,22 @@ impl BatchTransitionWasm { self.0.serialize_to_bytes().map_err(Into::into) } + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> WasmDppResult { + serialization::to_object(&self.0) + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> WasmDppResult { + serialization::to_json(&self.0) + } + #[wasm_bindgen(js_name = "toHex")] pub fn to_hex(&self) -> WasmDppResult { Ok(encode(self.to_bytes()?.as_slice(), Hex)) } - #[wasm_bindgen(js_name = "base64")] + #[wasm_bindgen(js_name = "toBase64")] pub fn to_base64(&self) -> WasmDppResult { Ok(encode(self.to_bytes()?.as_slice(), Base64)) } @@ -236,6 +207,16 @@ impl BatchTransitionWasm { Ok(BatchTransitionWasm::from(rs_batch)) } + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + serialization::from_object(js_value).map(BatchTransitionWasm) + } + + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + serialization::from_json(js_value).map(BatchTransitionWasm) + } + #[wasm_bindgen(js_name = "fromBase64")] pub fn from_base64(base64: String) -> WasmDppResult { BatchTransitionWasm::from_bytes( diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transition.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transition.rs index f65ffc1d4d2..d3aa3f25568 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transition.rs @@ -7,7 +7,7 @@ use crate::state_transitions::batch::document_transitions::update_price::Documen use crate::enums::batch::batch_enum::BatchTypeWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use dpp::prelude::{Identifier, IdentityNonce, Revision}; +use dpp::prelude::{IdentityNonce, Revision}; use dpp::state_transition::batch_transition::batched_transition::document_transition::{ DocumentTransition, DocumentTransitionV0Methods, }; @@ -182,25 +182,3 @@ impl DocumentTransitionWasm { self.0.set_identity_contract_nonce(identity_contract_nonce) } } - -impl DocumentTransitionWasm { - pub fn rs_get_data_contract_id(&self) -> Identifier { - self.0.data_contract_id() - } - - pub fn rs_get_id(&self) -> Identifier { - self.0.get_id() - } - - pub fn rs_get_entropy(&self) -> Option> { - self.0.entropy() - } - - pub fn rs_get_revision(&self) -> Option { - self.0.revision() - } - - pub fn rs_get_identity_contract_nonce(&self) -> IdentityNonce { - self.0.identity_contract_nonce() - } -} diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/create.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/create.rs index a4eb1c473a3..5ea2568586f 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/create.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/create.rs @@ -4,9 +4,9 @@ use crate::state_transitions::batch::generators::generate_create_transition; use crate::state_transitions::batch::prefunded_voting_balance::PrefundedVotingBalanceWasm; use crate::state_transitions::batch::token_payment_info::TokenPaymentInfoWasm; use crate::data_contract::document::DocumentWasm; -use crate::error::{WasmDppError, WasmDppResult}; +use crate::error::WasmDppResult; +use crate::serialization; use crate::utils::{IntoWasm, ToSerdeJSONExt}; -use dpp::dashcore::hashes::serde::Serialize; use dpp::prelude::IdentityNonce; use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; use dpp::state_transition::batch_transition::document_base_transition::document_base_transition_trait::DocumentBaseTransitionAccessors; @@ -72,7 +72,7 @@ impl DocumentCreateTransitionWasm { }; let rs_create_transition = generate_create_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), prefunded_voting_balance, @@ -84,12 +84,7 @@ impl DocumentCreateTransitionWasm { #[wasm_bindgen(getter = "data")] pub fn get_data(&self) -> WasmDppResult { - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - - self.0 - .data() - .serialize(&serializer) - .map_err(|err| WasmDppError::serialization(err.to_string())) + serialization::to_object(self.0.data()) } #[wasm_bindgen(getter = "base")] diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/delete.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/delete.rs index 79d23c1a44c..1131cc2fca6 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/delete.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/delete.rs @@ -50,7 +50,7 @@ impl DocumentDeleteTransitionWasm { }; let rs_delete_transition = generate_delete_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), token_payment_info, diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/purchase.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/purchase.rs index 19e9575b24f..c4db86bc46c 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/purchase.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/purchase.rs @@ -59,7 +59,7 @@ impl DocumentPurchaseTransitionWasm { }; let rs_purchase_transition = generate_purchase_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), amount, diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/replace.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/replace.rs index 33b9ff298fe..3b4ffc29b9d 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/replace.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/replace.rs @@ -3,9 +3,9 @@ use crate::state_transitions::batch::document_transition::DocumentTransitionWasm use crate::state_transitions::batch::generators::generate_replace_transition; use crate::state_transitions::batch::token_payment_info::TokenPaymentInfoWasm; use crate::data_contract::document::DocumentWasm; -use crate::error::{WasmDppError, WasmDppResult}; +use crate::error::WasmDppResult; +use crate::serialization; use crate::utils::{IntoWasm, ToSerdeJSONExt}; -use dpp::dashcore::hashes::serde::Serialize; use dpp::prelude::{IdentityNonce, Revision}; use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; use dpp::state_transition::batch_transition::document_base_transition::document_base_transition_trait::DocumentBaseTransitionAccessors; @@ -58,7 +58,7 @@ impl DocumentReplaceTransitionWasm { }; let rs_update_transition = generate_replace_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), token_payment_info, @@ -69,12 +69,7 @@ impl DocumentReplaceTransitionWasm { #[wasm_bindgen(getter = "data")] pub fn get_data(&self) -> WasmDppResult { - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - - self.0 - .data() - .serialize(&serializer) - .map_err(|err| WasmDppError::serialization(err.to_string())) + serialization::to_object(self.0.data()) } #[wasm_bindgen(getter = "base")] diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/transfer.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/transfer.rs index 0cd18a0abb7..cf3a78a04f3 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/transfer.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/transfer.rs @@ -60,7 +60,7 @@ impl DocumentTransferTransitionWasm { }; let rs_transfer_transition = generate_transfer_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), IdentifierWasm::try_from(js_recipient_owner_id)?.into(), diff --git a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/update_price.rs b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/update_price.rs index 168ee2fe56e..89160085764 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/update_price.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/document_transitions/update_price.rs @@ -53,7 +53,7 @@ impl DocumentUpdatePriceTransitionWasm { }; let rs_document_update_price_transition = generate_update_price_transition( - document.clone(), + document, identity_contract_nonce, document.get_document_type_name().to_string(), price, diff --git a/packages/wasm-dpp2/src/state_transitions/batch/generators.rs b/packages/wasm-dpp2/src/state_transitions/batch/generators.rs index d10f2203f43..06d3d8c9df5 100644 --- a/packages/wasm-dpp2/src/state_transitions/batch/generators.rs +++ b/packages/wasm-dpp2/src/state_transitions/batch/generators.rs @@ -1,6 +1,7 @@ use crate::data_contract::document::DocumentWasm; use crate::state_transitions::batch::prefunded_voting_balance::PrefundedVotingBalanceWasm; use crate::state_transitions::batch::token_payment_info::TokenPaymentInfoWasm; +use dpp::document::DocumentV0Getters; use dpp::fee::Credits; use dpp::prelude::{Identifier, IdentityNonce}; use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransitionV0; @@ -20,7 +21,7 @@ use dpp::state_transition::batch_transition::{ use dpp::tokens::token_payment_info::TokenPaymentInfo; pub fn generate_create_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, prefunded_voting_balance: Option, @@ -28,56 +29,56 @@ pub fn generate_create_transition( ) -> DocumentCreateTransition { DocumentCreateTransition::V0(DocumentCreateTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), - entropy: document.rs_get_entropy().unwrap(), - data: document.rs_get_properties(), + entropy: document.entropy.unwrap(), + data: document.document.properties().clone(), prefunded_voting_balance: prefunded_voting_balance.map(|pb| pb.into()), }) } pub fn generate_delete_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, token_payment_info: Option, ) -> DocumentDeleteTransition { DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), }) } pub fn generate_replace_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, token_payment_info: Option, ) -> DocumentReplaceTransition { DocumentReplaceTransition::V0(DocumentReplaceTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), - revision: document.get_revision().unwrap() + 1, - data: document.rs_get_properties(), + revision: document.document.revision().unwrap() + 1, + data: document.document.properties().clone(), }) } pub fn generate_transfer_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, recipient_owner_id: Identifier, @@ -85,19 +86,19 @@ pub fn generate_transfer_transition( ) -> DocumentTransferTransition { DocumentTransferTransition::V0(DocumentTransferTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), - revision: document.get_revision().unwrap() + 1, + revision: document.document.revision().unwrap() + 1, recipient_owner_id, }) } pub fn generate_update_price_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, price: Credits, @@ -105,19 +106,19 @@ pub fn generate_update_price_transition( ) -> DocumentUpdatePriceTransition { DocumentUpdatePriceTransition::V0(DocumentUpdatePriceTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), - revision: document.get_revision().unwrap() + 1, + revision: document.document.revision().unwrap() + 1, price, }) } pub fn generate_purchase_transition( - document: DocumentWasm, + document: &DocumentWasm, identity_contract_nonce: IdentityNonce, document_type_name: String, price: Credits, @@ -125,13 +126,13 @@ pub fn generate_purchase_transition( ) -> DocumentPurchaseTransition { DocumentPurchaseTransition::V0(DocumentPurchaseTransitionV0 { base: DocumentBaseTransition::V1(DocumentBaseTransitionV1 { - id: document.rs_get_id(), + id: document.document.id(), identity_contract_nonce, document_type_name, - data_contract_id: document.rs_get_data_contract_id(), + data_contract_id: document.data_contract_id.into(), token_payment_info: token_payment_info.map(TokenPaymentInfo::from), }), - revision: document.get_revision().unwrap() + 1, + revision: document.document.revision().unwrap() + 1, price, }) } diff --git a/packages/wasm-dpp2/src/tokens/configuration/distribution_recipient.rs b/packages/wasm-dpp2/src/tokens/configuration/distribution_recipient.rs index ff6dd8cb14e..94ba5ccf447 100644 --- a/packages/wasm-dpp2/src/tokens/configuration/distribution_recipient.rs +++ b/packages/wasm-dpp2/src/tokens/configuration/distribution_recipient.rs @@ -62,7 +62,7 @@ impl TokenDistributionRecipientWasm { } TokenDistributionRecipient::ContractOwner => String::from("ContractOwner"), TokenDistributionRecipient::Identity(identity) => { - format!("Identity({})", IdentifierWasm::from(identity).get_base58()) + format!("Identity({})", IdentifierWasm::from(identity).to_base58()) } } } diff --git a/packages/wasm-dpp2/src/tokens/configuration/group.rs b/packages/wasm-dpp2/src/tokens/configuration/group.rs index 1f8420adeda..32f6dd8bc67 100644 --- a/packages/wasm-dpp2/src/tokens/configuration/group.rs +++ b/packages/wasm-dpp2/src/tokens/configuration/group.rs @@ -1,5 +1,6 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::serialization; use crate::utils::JsValueExt; use dpp::data_contract::group::accessors::v0::{GroupV0Getters, GroupV0Setters}; use dpp::data_contract::group::v0::GroupV0; @@ -142,4 +143,27 @@ impl GroupWasm { Ok(()) } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> WasmDppResult { + serialization::to_json(&self.0) + } + + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + serialization::from_json(js_value).map(GroupWasm) + } + + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> WasmDppResult { + // Use toJSON for serialization because it handles BTreeMap + // correctly (Identifier becomes base58 string in human-readable mode). + // This ensures all fields are automatically included when new versions are added. + serialization::to_json(&self.0) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + serialization::from_object(js_value).map(GroupWasm) + } } diff --git a/packages/wasm-dpp2/src/tokens/configuration/localization.rs b/packages/wasm-dpp2/src/tokens/configuration/localization.rs index 5014162b889..f5f2715c627 100644 --- a/packages/wasm-dpp2/src/tokens/configuration/localization.rs +++ b/packages/wasm-dpp2/src/tokens/configuration/localization.rs @@ -1,11 +1,11 @@ -use crate::error::{WasmDppError, WasmDppResult}; -use crate::utils::{IntoWasm, JsValueExt}; +use crate::error::WasmDppResult; +use crate::serialization; +use crate::utils::IntoWasm; use dpp::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; use dpp::data_contract::associated_token::token_configuration_localization::accessors::v0::{ TokenConfigurationLocalizationV0Getters, TokenConfigurationLocalizationV0Setters, }; use dpp::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0; -use js_sys::{Object, Reflect}; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -84,46 +84,22 @@ impl TokenConfigurationLocalizationWasm { #[wasm_bindgen(js_name = "toJSON")] pub fn to_json(&self) -> WasmDppResult { - let object = Object::new(); - - Reflect::set( - &object, - &JsValue::from("shouldCapitalize"), - &JsValue::from(self.0.should_capitalize()), - ) - .map_err(|err| { - let message = err.error_message(); - WasmDppError::generic(format!( - "unable to set 'shouldCapitalize' on TokenConfigurationLocalization: {}", - message - )) - })?; - Reflect::set( - &object, - &JsValue::from("pluralForm"), - &JsValue::from(self.0.plural_form()), - ) - .map_err(|err| { - let message = err.error_message(); - WasmDppError::generic(format!( - "unable to set 'pluralForm' on TokenConfigurationLocalization: {}", - message - )) - })?; - Reflect::set( - &object, - &JsValue::from("singularForm"), - &JsValue::from(self.0.singular_form()), - ) - .map_err(|err| { - let message = err.error_message(); - WasmDppError::generic(format!( - "unable to set 'singularForm' on TokenConfigurationLocalization: {}", - message - )) - })?; - - Ok(object.into()) + serialization::to_json(&self.0) + } + + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js_value: JsValue) -> WasmDppResult { + serialization::from_json(js_value).map(TokenConfigurationLocalizationWasm) + } + + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> WasmDppResult { + serialization::to_object(&self.0) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(js_value: JsValue) -> WasmDppResult { + serialization::from_object(js_value).map(TokenConfigurationLocalizationWasm) } } @@ -131,79 +107,16 @@ impl TokenConfigurationLocalizationWasm { pub(crate) fn from_js_value( js_value: &JsValue, ) -> WasmDppResult { - match js_value - .to_wasm::("TokenConfigurationLocalization") + // First, check if it's already a WASM wrapper + if let Ok(wasm_localization) = + js_value.to_wasm::("TokenConfigurationLocalization") { - Ok(wasm_localization) => Ok(TokenConfigurationLocalization::from( + return Ok(TokenConfigurationLocalization::from( wasm_localization.clone(), - )), - Err(err) => { - let message = err.message(); - - if message.contains("constructor name mismatch") { - localization_from_plain_js_value(js_value) - } else { - Err(err) - } - } + )); } - } -} -fn localization_from_plain_js_value( - js_value: &JsValue, -) -> WasmDppResult { - if !js_value.is_object() { - return Err(WasmDppError::invalid_argument( - "TokenConfigurationLocalization must be an object", - )); - } - - let should_capitalize_value = Reflect::get(js_value, &JsValue::from_str("shouldCapitalize")) - .map_err(|err| { - let message = err.error_message(); - WasmDppError::invalid_argument(format!( - "TokenConfigurationLocalization.shouldCapitalize could not be read: {}", - message - )) - })?; - let should_capitalize = should_capitalize_value.as_bool().ok_or_else(|| { - WasmDppError::invalid_argument( - "TokenConfigurationLocalization.shouldCapitalize must be a boolean", - ) - })?; - - let singular_form_value = - Reflect::get(js_value, &JsValue::from_str("singularForm")).map_err(|err| { - let message = err.error_message(); - WasmDppError::invalid_argument(format!( - "TokenConfigurationLocalization.singularForm could not be read: {}", - message - )) - })?; - let singular_form = singular_form_value.as_string().ok_or_else(|| { - WasmDppError::invalid_argument( - "TokenConfigurationLocalization.singularForm must be a string", - ) - })?; - - let plural_form_value = - Reflect::get(js_value, &JsValue::from_str("pluralForm")).map_err(|err| { - let message = err.error_message(); - WasmDppError::invalid_argument(format!( - "TokenConfigurationLocalization.pluralForm could not be read: {}", - message - )) - })?; - let plural_form = plural_form_value.as_string().ok_or_else(|| { - WasmDppError::invalid_argument("TokenConfigurationLocalization.pluralForm must be a string") - })?; - - Ok(TokenConfigurationLocalization::V0( - TokenConfigurationLocalizationV0 { - should_capitalize, - singular_form, - plural_form, - }, - )) + // Deserialize as a versioned object (with $format_version) + serialization::from_object(js_value.clone()) + } } diff --git a/packages/wasm-dpp2/src/tokens/configuration/pre_programmed_distribution.rs b/packages/wasm-dpp2/src/tokens/configuration/pre_programmed_distribution.rs index 629273ac076..b0f435e4ac3 100644 --- a/packages/wasm-dpp2/src/tokens/configuration/pre_programmed_distribution.rs +++ b/packages/wasm-dpp2/src/tokens/configuration/pre_programmed_distribution.rs @@ -114,7 +114,7 @@ impl TokenPreProgrammedDistributionWasm { for (identifiers_key, identifiers_value) in value { Reflect::set( &identifiers_obj, - &IdentifierWasm::from(*identifiers_key).get_base58().into(), + &IdentifierWasm::from(*identifiers_key).to_base58().into(), &BigInt::from(*identifiers_value).into(), ) .map_err(|err| { diff --git a/packages/wasm-dpp2/src/utils.rs b/packages/wasm-dpp2/src/utils.rs index 7ef9eefdbe3..65621a0d5a4 100644 --- a/packages/wasm-dpp2/src/utils.rs +++ b/packages/wasm-dpp2/src/utils.rs @@ -1,27 +1,16 @@ use crate::error::{WasmDppError, WasmDppResult}; -use anyhow::{Context, anyhow, bail}; +use anyhow::{anyhow, bail}; use dpp::identifier::Identifier; use dpp::platform_value::Value; use dpp::util::hash::hash_double_to_vec; -use js_sys::{Error as JsError, Function}; +use js_sys::Error as JsError; use serde_json::Value as JsonValue; use std::collections::BTreeMap; use std::convert::TryInto; use wasm_bindgen::convert::RefFromWasmAbi; use wasm_bindgen::{JsCast, JsValue}; -pub fn stringify_wasm(data: &JsValue) -> WasmDppResult { - let replacer_func = Function::new_with_args( - "key, value", - "return (value != undefined && value.type=='Buffer') ? value.data : value ", - ); - - let data_string = js_sys::JSON::stringify_with_replacer(data, &JsValue::from(replacer_func)) - .map_err(|_| WasmDppError::serialization("Failed to stringify value"))?; - - Ok(data_string.into()) -} - +/// Extension trait for extracting error messages from JsValue pub trait JsValueExt { fn error_message(&self) -> String; } @@ -47,42 +36,22 @@ impl JsValueExt for JsValue { } } -pub fn with_serde_to_json_value_wasm(data: JsValue) -> WasmDppResult { - let data = stringify_wasm(&data)?; - serde_json::from_str(&data).map_err(|e| { - WasmDppError::serialization(format!( - "unable to convert value to serde_json::Value: {e:#}" - )) - }) -} - -pub fn with_serde_to_platform_value_wasm(data: &JsValue) -> WasmDppResult { - Ok(with_serde_to_json_value_wasm(data.clone())?.into()) -} - +/// Extension trait for converting JsValue to serde/platform values pub trait ToSerdeJSONExt { fn with_serde_to_json_value(&self) -> WasmDppResult; fn with_serde_to_platform_value(&self) -> WasmDppResult; - /// Converts the `JsValue` into `platform::Value`. It's an expensive conversion, - /// as `JsValue` must be stringified first fn with_serde_to_platform_value_map(&self) -> WasmDppResult>; } impl ToSerdeJSONExt for JsValue { - /// Converts the `JsValue` into `serde_json::Value`. It's an expensive conversion, - /// as `JsValue` must be stringified first fn with_serde_to_json_value(&self) -> WasmDppResult { - with_serde_to_json_value(self.clone()) + crate::serialization::js_value_to_json(self) } - /// Converts the `JsValue` into `platform::Value`. It's an expensive conversion, - /// as `JsValue` must be stringified first fn with_serde_to_platform_value(&self) -> WasmDppResult { - with_serde_to_platform_value(self) + Ok(self.with_serde_to_json_value()?.into()) } - /// Converts the `JsValue` into `platform::Value`. It's an expensive conversion, - /// as `JsValue` must be stringified first fn with_serde_to_platform_value_map(&self) -> WasmDppResult> { self.with_serde_to_platform_value()? .into_btree_string_map() @@ -90,66 +59,7 @@ impl ToSerdeJSONExt for JsValue { } } -pub fn to_vec_js(iter: impl IntoIterator) -> Vec -where - T: Into, -{ - iter.into_iter().map(|v| v.into()).collect() -} - -#[allow(dead_code)] -#[deprecated(note = "This function is marked as unused.")] -#[allow(deprecated)] -pub fn to_vec_of_serde_values( - values: impl IntoIterator>, -) -> WasmDppResult> { - values - .into_iter() - .map(|v| v.as_ref().with_serde_to_json_value()) - .collect() -} - -pub fn to_vec_of_platform_values( - values: impl IntoIterator>, -) -> WasmDppResult> { - values - .into_iter() - .map(|v| v.as_ref().with_serde_to_platform_value()) - .collect() -} - -pub fn with_serde_to_json_value(data: JsValue) -> WasmDppResult { - let data = stringify(&data)?; - let value: JsonValue = serde_json::from_str(&data) - .with_context(|| format!("cant convert {data:#?} to serde json value")) - .map_err(|e| WasmDppError::serialization(format!("{e:#}")))?; - Ok(value) -} - -pub fn with_serde_to_platform_value(data: &JsValue) -> WasmDppResult { - Ok(with_serde_to_json_value(data.clone())?.into()) -} - -pub fn stringify(data: &JsValue) -> WasmDppResult { - let replacer_func = Function::new_with_args( - "key, value", - "return (value != undefined && value.type=='Buffer') ? value.data : value ", - ); - - let data_string: String = - js_sys::JSON::stringify_with_replacer(data, &JsValue::from(replacer_func)) - .map_err(|err| { - let message = err.error_message(); - WasmDppError::serialization(format!( - "unable to stringify value to JSON: {}", - message - )) - })? - .into(); - - Ok(data_string) -} - +/// Trait for converting JsValue to a WASM type by reading its internal pointer pub trait IntoWasm { fn to_wasm>(&self, class_name: &str) -> WasmDppResult; } @@ -160,6 +70,7 @@ impl IntoWasm for JsValue { } } +/// Convert a JsValue to a WASM type by reading its internal pointer pub fn generic_of_js_val>( js_value: &JsValue, class_name: &str, @@ -197,6 +108,7 @@ pub fn generic_of_js_val>( } } +/// Get the `__type` property from a JsValue (used for WASM class identification) pub fn get_class_type(value: &JsValue) -> WasmDppResult { let class_type = js_sys::Reflect::get(value, &JsValue::from_str("__type")).map_err(|err| { let message = err.error_message(); @@ -209,6 +121,7 @@ pub fn get_class_type(value: &JsValue) -> WasmDppResult { Ok(class_type.as_string().unwrap_or_default()) } +/// Convert a JS Number or BigInt to u64 pub fn try_to_u64(value: JsValue) -> Result { if value.is_bigint() { js_sys::BigInt::new(&value) @@ -223,19 +136,20 @@ pub fn try_to_u64(value: JsValue) -> Result { } } -pub fn convert_number_to_u64(js_number: js_sys::Number) -> Result { +/// Convert a JS Number to u64 with validation +fn convert_number_to_u64(js_number: js_sys::Number) -> Result { if let Some(float_number) = js_number.as_f64() { if float_number.is_nan() || float_number.is_infinite() { - bail!("received an invalid timestamp: the number is either NaN or Inf") + bail!("received an invalid number: the number is either NaN or Inf") } if float_number < 0. { - bail!("received an invalid timestamp: the number is negative"); + bail!("received an invalid number: the number is negative"); } if float_number.fract() != 0. { - bail!("received an invalid timestamp: the number is fractional") + bail!("received an invalid number: the number is fractional") } if float_number > u64::MAX as f64 { - bail!("received an invalid timestamp: the number is > u64::max") + bail!("received an invalid number: the number is > u64::max") } return Ok(float_number as u64); @@ -243,6 +157,7 @@ pub fn convert_number_to_u64(js_number: js_sys::Number) -> Result for ResourceVoteWasm { + fn from(vote: ResourceVote) -> Self { + Self(vote) + } +} + +impl From for ResourceVote { + fn from(vote: ResourceVoteWasm) -> Self { + vote.0 + } +} + +#[wasm_bindgen(js_class = ResourceVote)] +impl ResourceVoteWasm { + #[wasm_bindgen(getter = __type)] + pub fn type_name(&self) -> String { + "ResourceVote".to_string() + } + + #[wasm_bindgen(getter = __struct)] + pub fn struct_name() -> String { + "ResourceVote".to_string() + } + + #[wasm_bindgen(constructor)] + pub fn new(vote_poll: &VotePollWasm, choice: &ResourceVoteChoiceWasm) -> Self { + ResourceVoteWasm(ResourceVote::V0(ResourceVoteV0 { + vote_poll: vote_poll.clone().into(), + resource_vote_choice: choice.clone().into(), + })) + } + + #[wasm_bindgen(getter = votePoll)] + pub fn vote_poll(&self) -> VotePollWasm { + self.0.vote_poll().clone().into() + } + + #[wasm_bindgen(getter = choice)] + pub fn choice(&self) -> ResourceVoteChoiceWasm { + self.0.resource_vote_choice().into() + } + + #[wasm_bindgen(setter = votePoll)] + pub fn set_vote_poll(&mut self, vote_poll: &VotePollWasm) { + let ResourceVote::V0(ResourceVoteV0 { + resource_vote_choice, + .. + }) = self.0.clone(); + + self.0 = ResourceVote::V0(ResourceVoteV0 { + vote_poll: vote_poll.clone().into(), + resource_vote_choice, + }); + } + + #[wasm_bindgen(setter = choice)] + pub fn set_choice(&mut self, choice: &ResourceVoteChoiceWasm) { + let ResourceVote::V0(ResourceVoteV0 { vote_poll, .. }) = self.0.clone(); + + self.0 = ResourceVote::V0(ResourceVoteV0 { + vote_poll, + resource_vote_choice: choice.clone().into(), + }); + } +} + +impl_wasm_conversions!(ResourceVoteWasm, ResourceVote); diff --git a/packages/wasm-dpp2/src/voting/resource_vote_choice.rs b/packages/wasm-dpp2/src/voting/resource_vote_choice.rs index a024a9d5b02..0c480c2f0f8 100644 --- a/packages/wasm-dpp2/src/voting/resource_vote_choice.rs +++ b/packages/wasm-dpp2/src/voting/resource_vote_choice.rs @@ -1,5 +1,6 @@ use crate::error::WasmDppResult; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -71,3 +72,5 @@ impl ResourceVoteChoiceWasm { } } } + +impl_wasm_conversions!(ResourceVoteChoiceWasm, ResourceVoteChoice); diff --git a/packages/wasm-dpp2/src/voting/vote.rs b/packages/wasm-dpp2/src/voting/vote.rs index 0edf9ac25ed..2e605d63848 100644 --- a/packages/wasm-dpp2/src/voting/vote.rs +++ b/packages/wasm-dpp2/src/voting/vote.rs @@ -1,3 +1,4 @@ +use crate::impl_wasm_conversions; use crate::voting::resource_vote_choice::ResourceVoteChoiceWasm; use crate::voting::vote_poll::VotePollWasm; use dpp::voting::votes::Vote; @@ -76,3 +77,5 @@ impl VoteWasm { } } } + +impl_wasm_conversions!(VoteWasm, Vote); diff --git a/packages/wasm-dpp2/src/voting/vote_poll.rs b/packages/wasm-dpp2/src/voting/vote_poll.rs index 2d1a644cd68..6aaf32e594f 100644 --- a/packages/wasm-dpp2/src/voting/vote_poll.rs +++ b/packages/wasm-dpp2/src/voting/vote_poll.rs @@ -1,5 +1,6 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use crate::utils::ToSerdeJSONExt; use dpp::bincode; use dpp::voting::vote_polls::VotePoll; @@ -176,3 +177,5 @@ impl VotePollWasm { Ok(()) } } + +impl_wasm_conversions!(VotePollWasm, VotePoll); diff --git a/packages/wasm-dpp2/src/voting/winner_info.rs b/packages/wasm-dpp2/src/voting/winner_info.rs index 645e678a36f..858d84d8d3e 100644 --- a/packages/wasm-dpp2/src/voting/winner_info.rs +++ b/packages/wasm-dpp2/src/voting/winner_info.rs @@ -1,4 +1,5 @@ use crate::identifier::IdentifierWasm; +use crate::impl_wasm_conversions; use dpp::voting::vote_info_storage::contested_document_vote_poll_winner_info::ContestedDocumentVotePollWinnerInfo; use wasm_bindgen::JsValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -27,17 +28,18 @@ impl ContestedDocumentVotePollWinnerInfoWasm { identity_id: Option, ) -> Result { match kind { - "none" | "NoWinner" | "NO_WINNER" => { + "NoWinner" | "noWinner" | "no_winner" | "none" | "NO_WINNER" => { Ok(ContestedDocumentVotePollWinnerInfo::NoWinner.into()) } - "identity" | "Identity" | "IDENTITY" => { + "WonByIdentity" | "wonByIdentity" | "won_by_identity" | "identity" | "Identity" + | "IDENTITY" => { let identity = identity_id.ok_or_else(|| { - JsValue::from_str("identityId is required when kind is 'identity'") + JsValue::from_str("identityId is required when kind is 'WonByIdentity'") })?; Ok(ContestedDocumentVotePollWinnerInfo::WonByIdentity(identity.into()).into()) } - "locked" | "Locked" | "LOCKED" => { + "Locked" | "locked" | "LOCKED" => { Ok(ContestedDocumentVotePollWinnerInfo::Locked.into()) } other => Err(JsValue::from_str(&format!( @@ -50,9 +52,9 @@ impl ContestedDocumentVotePollWinnerInfoWasm { #[wasm_bindgen(getter = kind)] pub fn kind(&self) -> String { match self.0 { - ContestedDocumentVotePollWinnerInfo::NoWinner => "none".to_string(), - ContestedDocumentVotePollWinnerInfo::WonByIdentity(_) => "identity".to_string(), - ContestedDocumentVotePollWinnerInfo::Locked => "locked".to_string(), + ContestedDocumentVotePollWinnerInfo::NoWinner => "NoWinner".to_string(), + ContestedDocumentVotePollWinnerInfo::WonByIdentity(_) => "WonByIdentity".to_string(), + ContestedDocumentVotePollWinnerInfo::Locked => "Locked".to_string(), } } @@ -85,6 +87,11 @@ impl ContestedDocumentVotePollWinnerInfoWasm { } } +impl_wasm_conversions!( + ContestedDocumentVotePollWinnerInfoWasm, + ContestedDocumentVotePollWinnerInfo +); + impl ContestedDocumentVotePollWinnerInfoWasm { pub fn into_inner(self) -> ContestedDocumentVotePollWinnerInfo { self.0 diff --git a/packages/wasm-dpp2/tests/unit/AssetLockProof.spec.mjs b/packages/wasm-dpp2/tests/unit/AssetLockProof.spec.mjs index af3f6028545..34ec18995e1 100644 --- a/packages/wasm-dpp2/tests/unit/AssetLockProof.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/AssetLockProof.spec.mjs @@ -58,6 +58,100 @@ describe('AssetLockProof', () => { expect(newInstantLockProof.toObject()).to.deep.equal(instantLockProof.toObject()); }); + + it('should allow to serialize and deserialize asset lock in hex for chain proofs', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + const chainLockProof = wasm.AssetLockProof.createChainAssetLockProof(1, outpoint); + + const newChainLockProof = wasm.AssetLockProof.fromHex(chainLockProof.toHex()); + + expect(chainLockProof.constructor.name).to.equal('AssetLockProof'); + expect(newChainLockProof.constructor.name).to.equal('AssetLockProof'); + + expect(newChainLockProof.toObject()).to.deep.equal(chainLockProof.toObject()); + }); + + it('should round-trip asset lock via bytes for instant proofs', () => { + const instantLockProof = wasm.AssetLockProof.createInstantAssetLockProof(instantLockBytes, transactionBytes, 0); + + const bytes = instantLockProof.toBytes(); + const restored = wasm.AssetLockProof.fromBytes(bytes); + + expect(restored.toObject()).to.deep.equal(instantLockProof.toObject()); + }); + + it('should round-trip asset lock via bytes for chain proofs', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + const chainLockProof = wasm.AssetLockProof.createChainAssetLockProof(1, outpoint); + + const bytes = chainLockProof.toBytes(); + const restored = wasm.AssetLockProof.fromBytes(bytes); + + expect(restored.toObject()).to.deep.equal(chainLockProof.toObject()); + }); + + it('should recreate asset lock proof from object', () => { + const instantLockProof = wasm.AssetLockProof.createInstantAssetLockProof(instantLockBytes, transactionBytes, 0); + const objectRepresentation = instantLockProof.toObject(); + + const restoredProof = wasm.AssetLockProof.fromObject(objectRepresentation); + + expect(restoredProof.toObject()).to.deep.equal(objectRepresentation); + }); + + it('should recreate asset lock proof from JSON', () => { + const instantLockProof = wasm.AssetLockProof.createInstantAssetLockProof(instantLockBytes, transactionBytes, 0); + const jsonRepresentation = instantLockProof.toJSON(); + + // Should have type field (0 = Instant) + expect(jsonRepresentation.type).to.equal(0); + expect(jsonRepresentation.instantLock).to.be.a('string'); + expect(jsonRepresentation.transaction).to.be.a('string'); + expect(Buffer.from(jsonRepresentation.instantLock, 'base64')).to.deep.equal(Buffer.from(instantLockBytes)); + expect(Buffer.from(jsonRepresentation.transaction, 'base64')).to.deep.equal(Buffer.from(transactionBytes)); + + const restoredProof = wasm.AssetLockProof.fromJSON(jsonRepresentation); + + expect(restoredProof.toObject()).to.deep.equal(instantLockProof.toObject()); + }); + + it('should recreate chain asset lock proof from JSON', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + const chainLockProof = wasm.AssetLockProof.createChainAssetLockProof(1, outpoint); + const jsonRepresentation = chainLockProof.toJSON(); + + // Should have type field (1 = Chain) + expect(jsonRepresentation.type).to.equal(1); + + const restoredProof = wasm.AssetLockProof.fromJSON(jsonRepresentation); + + expect(restoredProof.toObject()).to.deep.equal(chainLockProof.toObject()); + }); + + it('should export binary fields as Uint8Array in object form with type field', () => { + const instantLockProof = wasm.AssetLockProof.createInstantAssetLockProof(instantLockBytes, transactionBytes, 0); + + const objectRepresentation = instantLockProof.toObject(); + + // Should have type field (0 = Instant) + expect(objectRepresentation.type).to.equal(0); + expect(objectRepresentation.instantLock).to.be.instanceOf(Uint8Array); + expect(objectRepresentation.transaction).to.be.instanceOf(Uint8Array); + expect(objectRepresentation.instantLock).to.deep.equal(instantLockBytes); + expect(objectRepresentation.transaction).to.deep.equal(transactionBytes); + }); + + it('should export chain lock proof object with type field', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + const chainLockProof = wasm.AssetLockProof.createChainAssetLockProof(1, outpoint); + + const objectRepresentation = chainLockProof.toObject(); + + // Should have type field (1 = Chain) + expect(objectRepresentation.type).to.equal(1); + expect(objectRepresentation.coreChainLockedHeight).to.equal(1); + expect(objectRepresentation.outPoint).to.be.instanceOf(Uint8Array); + }); }); describe('getters', () => { @@ -88,14 +182,21 @@ describe('AssetLockProof', () => { const instantAssetLockProof = new wasm.AssetLockProof(instantLockProof); - const expected = { + // InstantAssetLockProof.toObject() does not include type field + const expectedInner = { instantLock: instantLockBytes, transaction: transactionBytes, outputIndex: 0, }; - expect(instantLockProof.toObject()).to.deep.equal(expected); - expect(instantAssetLockProof.toObject()).to.deep.equal(expected); + // AssetLockProof.toObject() includes type field (0 = Instant) + const expectedOuter = { + ...expectedInner, + type: 0, + }; + + expect(instantLockProof.toObject()).to.deep.equal(expectedInner); + expect(instantAssetLockProof.toObject()).to.deep.equal(expectedOuter); }); }); }); diff --git a/packages/wasm-dpp2/tests/unit/AuthorizedActionTakers.spec.mjs b/packages/wasm-dpp2/tests/unit/AuthorizedActionTakers.spec.mjs index 9a100ebb492..a4bdb274b1d 100644 --- a/packages/wasm-dpp2/tests/unit/AuthorizedActionTakers.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/AuthorizedActionTakers.spec.mjs @@ -61,7 +61,7 @@ describe('AuthorizedActionTakers', () => { it('should allows to get value with Identity', () => { const actionTaker = wasm.AuthorizedActionTakers.Identity(identifier); - expect(actionTaker.getValue().base58()).to.deep.equal(identifier); + expect(actionTaker.getValue().toBase58()).to.deep.equal(identifier); }); it('should allows to get value with MainGroup', () => { diff --git a/packages/wasm-dpp2/tests/unit/BatchTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/BatchTransition.spec.mjs index 50718c10a78..90abab7d8b9 100644 --- a/packages/wasm-dpp2/tests/unit/BatchTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/BatchTransition.spec.mjs @@ -18,7 +18,9 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batch = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1); + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batch = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(createTransition.__wbg_ptr).to.not.equal(0); @@ -34,7 +36,7 @@ describe('BatchTransition', () => { const batchedTransition = new wasm.BatchedTransition(documentTransition); - const batch = wasm.BatchTransition.fromV1BatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); + const batch = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(createTransition.__wbg_ptr).to.not.equal(0); @@ -42,8 +44,51 @@ describe('BatchTransition', () => { expect(batchedTransition.__wbg_ptr).to.not.equal(0); expect(batch.__wbg_ptr).to.not.equal(0); }); - }); - describe('tokens', () => { + + it('should allow to convert batch transition to base64 and back', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + const createTransition = new wasm.DocumentCreateTransition(documentInstance, BigInt(1)); + + const documentTransition = createTransition.toDocumentTransition(); + + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batch = wasm.BatchTransition.fromBatchedTransitions([batchedTransition], documentInstance.ownerId, 1); + + const base64 = batch.toBase64(); + const bytes = batch.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + + const restoredBatch = wasm.BatchTransition.fromBase64(base64); + + expect(Buffer.from(restoredBatch.toBytes())).to.deep.equal(Buffer.from(bytes)); + }); + + it('should round-trip via object and JSON', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + const createTransition = new wasm.DocumentCreateTransition(documentInstance, BigInt(1)); + + const documentTransition = createTransition.toDocumentTransition(); + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batch = wasm.BatchTransition.fromBatchedTransitions([batchedTransition], documentInstance.ownerId, 1); + + const object = batch.toObject(); + expect(object.signature).to.be.instanceOf(Uint8Array); + + // Note: fromObject with complex nested structures containing Value fields + // requires special handling due to serde_wasm_bindgen byte serialization. + // Use fromJSON for reliable round-trip serialization. + + const json = batch.toJSON(); + expect(json.signature).to.be.a('string'); + + const fromJson = wasm.BatchTransition.fromJSON(json); + expect(Buffer.from(fromJson.toBytes())).to.deep.equal(Buffer.from(batch.toBytes())); + }); + }); + describe('tokens', () => { it('should allow to create from v1 transition', () => { const baseTransition = new wasm.TokenBaseTransition(BigInt(1), 1, dataContractId, ownerId); @@ -53,7 +98,7 @@ describe('BatchTransition', () => { const batchedTransition = new wasm.BatchedTransition(transition); - const batch = wasm.BatchTransition.fromV1BatchedTransitions([batchedTransition, batchedTransition], ownerId, 1); + const batch = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], ownerId, 1); expect(baseTransition.__wbg_ptr).to.not.equal(0); expect(mintTransition.__wbg_ptr).to.not.equal(0); @@ -71,7 +116,9 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); expect(batchTransition.transitions.length).to.equal(2); }); @@ -82,7 +129,9 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); expect(batchTransition.signature).to.deep.equal(new Uint8Array(0)); }); @@ -93,7 +142,10 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchedTransition = new wasm.BatchedTransition(documentTransition); + + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([batchedTransition, batchedTransition], documentInstance.ownerId, 1); + batchTransition.signaturePublicKeyId = 1; expect(batchTransition.signaturePublicKeyId).to.equal(1); }); @@ -106,7 +158,7 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); const documentTransition2 = purchaseTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition2], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition2)], documentInstance.ownerId, 1); expect(batchTransition.allPurchasesAmount).to.deep.equal(BigInt(100)); }); @@ -117,9 +169,9 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); - expect(batchTransition.ownerId.base58()).to.deep.equal(documentInstance.ownerId.base58()); + expect(batchTransition.ownerId.toBase58()).to.deep.equal(documentInstance.ownerId.toBase58()); }); it('should allow to get modified data ids', () => { @@ -128,9 +180,9 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); - expect(batchTransition.modifiedDataIds.map((identifier) => identifier.base58())).to.deep.equal([documentTransition.id.base58(), documentTransition.id.base58()]); + expect(batchTransition.modifiedDataIds.map((identifier) => identifier.toBase58())).to.deep.equal([documentTransition.id.toBase58(), documentTransition.id.toBase58()]); }); it('should allow to get allConflictingIndexCollateralVotingFunds', () => { @@ -139,7 +191,7 @@ describe('BatchTransition', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(batchTransition.allConflictingIndexCollateralVotingFunds).to.deep.equal(undefined); }); diff --git a/packages/wasm-dpp2/tests/unit/BlockInfo.spec.mjs b/packages/wasm-dpp2/tests/unit/BlockInfo.spec.mjs new file mode 100644 index 00000000000..f8603eaca85 --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/BlockInfo.spec.mjs @@ -0,0 +1,107 @@ +import getWasm from './helpers/wasm.js'; + +let wasm; + +before(async () => { + wasm = await getWasm(); +}); + +describe('BlockInfo', () => { + describe('constructor', () => { + it('should create BlockInfo with all parameters', () => { + const blockInfo = new wasm.BlockInfo(1000n, 100n, 50, 5); + + expect(blockInfo.timeMs).to.equal(1000n); + expect(blockInfo.height).to.equal(100n); + expect(blockInfo.coreHeight).to.equal(50); + expect(blockInfo.epochIndex).to.equal(5); + }); + + it('should create BlockInfo with zero values', () => { + const blockInfo = new wasm.BlockInfo(0n, 0n, 0, 0); + + expect(blockInfo.timeMs).to.equal(0n); + expect(blockInfo.height).to.equal(0n); + expect(blockInfo.coreHeight).to.equal(0); + expect(blockInfo.epochIndex).to.equal(0); + }); + }); + + describe('serialization / deserialization', () => { + it('should round-trip via toJSON/fromJSON', () => { + const blockInfo = new wasm.BlockInfo(1234567890n, 999n, 500, 10); + + const json = blockInfo.toJSON(); + // serde uses snake_case by default + expect(json.time_ms).to.equal(1234567890); + expect(json.height).to.equal(999); + expect(json.core_height).to.equal(500); + expect(json.epoch.index).to.equal(10); + + const restored = wasm.BlockInfo.fromJSON(json); + expect(restored.timeMs).to.equal(blockInfo.timeMs); + expect(restored.height).to.equal(blockInfo.height); + expect(restored.coreHeight).to.equal(blockInfo.coreHeight); + expect(restored.epochIndex).to.equal(blockInfo.epochIndex); + }); + + it('should round-trip via toObject/fromObject', () => { + const blockInfo = new wasm.BlockInfo(1234567890n, 999n, 500, 10); + + const obj = blockInfo.toObject(); + // serde uses snake_case by default + // serde_wasm_bindgen serializes u64 as Number when it fits + expect(Number(obj.time_ms)).to.equal(1234567890); + expect(Number(obj.height)).to.equal(999); + expect(obj.core_height).to.equal(500); + expect(obj.epoch.index).to.equal(10); + + const restored = wasm.BlockInfo.fromObject(obj); + expect(restored.timeMs).to.equal(blockInfo.timeMs); + expect(restored.height).to.equal(blockInfo.height); + expect(restored.coreHeight).to.equal(blockInfo.coreHeight); + expect(restored.epochIndex).to.equal(blockInfo.epochIndex); + }); + + it('should handle large values correctly', () => { + // Test with BigInt-sized values + const largeTimeMs = 1702500000000n; // realistic timestamp in ms + const largeHeight = 1000000n; + const blockInfo = new wasm.BlockInfo(largeTimeMs, largeHeight, 800000, 100); + + // JSON round-trip + const json = blockInfo.toJSON(); + const restoredFromJson = wasm.BlockInfo.fromJSON(json); + expect(restoredFromJson.timeMs).to.equal(largeTimeMs); + expect(restoredFromJson.height).to.equal(largeHeight); + + // Object round-trip + const obj = blockInfo.toObject(); + const restoredFromObj = wasm.BlockInfo.fromObject(obj); + expect(restoredFromObj.timeMs).to.equal(largeTimeMs); + expect(restoredFromObj.height).to.equal(largeHeight); + }); + }); + + describe('getters', () => { + it('should return timeMs', () => { + const blockInfo = new wasm.BlockInfo(5000n, 100n, 50, 5); + expect(blockInfo.timeMs).to.equal(5000n); + }); + + it('should return height', () => { + const blockInfo = new wasm.BlockInfo(5000n, 100n, 50, 5); + expect(blockInfo.height).to.equal(100n); + }); + + it('should return coreHeight', () => { + const blockInfo = new wasm.BlockInfo(5000n, 100n, 50, 5); + expect(blockInfo.coreHeight).to.equal(50); + }); + + it('should return epochIndex', () => { + const blockInfo = new wasm.BlockInfo(5000n, 100n, 50, 5); + expect(blockInfo.epochIndex).to.equal(5); + }); + }); +}); diff --git a/packages/wasm-dpp2/tests/unit/ChainLockProof.spec.mjs b/packages/wasm-dpp2/tests/unit/ChainLockProof.spec.mjs index 7bccf6e1eb9..0700822529a 100644 --- a/packages/wasm-dpp2/tests/unit/ChainLockProof.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/ChainLockProof.spec.mjs @@ -17,13 +17,39 @@ describe('InstantLock', () => { it('should allow to create chain lock proof from object', () => { const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); - const chainlock = wasm.ChainAssetLockProof.fromRawObject({ + const chainlock = wasm.ChainAssetLockProof.fromObject({ coreChainLockedHeight: 11, - outPoint: Array.from(outpoint.toBytes()), + outPoint: outpoint.toBytes(), }); expect(chainlock.__wbg_ptr).to.not.equal(0); }); + + it('should round-trip via object/json/bytes', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + const chainlock = new wasm.ChainAssetLockProof(11, outpoint); + + const object = chainlock.toObject(); + expect(object.coreChainLockedHeight).to.equal(11); + expect(object.outPoint).to.be.instanceOf(Uint8Array); + + const fromObject = wasm.ChainAssetLockProof.fromObject(object); + expect(fromObject.coreChainLockedHeight).to.equal(11); + expect(Array.from(fromObject.outPoint.toBytes())).to.deep.equal(Array.from(outpoint.toBytes())); + + const json = chainlock.toJSON(); + expect(json.coreChainLockedHeight).to.equal(11); + expect(json.outPoint).to.be.a('string'); // serde_json human-readable outputs base64 + + const fromJson = wasm.ChainAssetLockProof.fromJSON(json); + expect(fromJson.coreChainLockedHeight).to.equal(11); + expect(Array.from(fromJson.outPoint.toBytes())).to.deep.equal(Array.from(outpoint.toBytes())); + + const bytes = chainlock.toBytes(); + const fromBytes = wasm.ChainAssetLockProof.fromBytes(bytes); + expect(fromBytes.coreChainLockedHeight).to.equal(11); + expect(Array.from(fromBytes.outPoint.toBytes())).to.deep.equal(Array.from(outpoint.toBytes())); + }); }); describe('getters', () => { diff --git a/packages/wasm-dpp2/tests/unit/ContestedDocumentVotePollWinnerInfo.spec.mjs b/packages/wasm-dpp2/tests/unit/ContestedDocumentVotePollWinnerInfo.spec.mjs new file mode 100644 index 00000000000..cc5432095ef --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/ContestedDocumentVotePollWinnerInfo.spec.mjs @@ -0,0 +1,134 @@ +import getWasm from './helpers/wasm.js'; + +let wasm; + +before(async () => { + wasm = await getWasm(); +}); + +describe('ContestedDocumentVotePollWinnerInfo', () => { + const identityIdHex = '1111111111111111111111111111111111111111111111111111111111111111'; + + describe('constructor', () => { + it('should create NoWinner info', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('NoWinner'); + + expect(info.kind).to.equal('NoWinner'); + expect(info.identityId).to.be.null; + expect(info.isNoWinner()).to.be.true; + expect(info.isWonByIdentity()).to.be.false; + expect(info.isLocked()).to.be.false; + }); + + it('should create WonByIdentity info', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const identityIdBase58 = identityId.toBase58(); + const info = new wasm.ContestedDocumentVotePollWinnerInfo('WonByIdentity', identityId); + + expect(info.kind).to.equal('WonByIdentity'); + expect(info.identityId).to.not.be.null; + expect(info.identityId.toBase58()).to.equal(identityIdBase58); + expect(info.isNoWinner()).to.be.false; + expect(info.isWonByIdentity()).to.be.true; + expect(info.isLocked()).to.be.false; + }); + + it('should create Locked info', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('Locked'); + + expect(info.kind).to.equal('Locked'); + expect(info.identityId).to.be.null; + expect(info.isNoWinner()).to.be.false; + expect(info.isWonByIdentity()).to.be.false; + expect(info.isLocked()).to.be.true; + }); + + it('should accept alternative kind names', () => { + const noWinner = new wasm.ContestedDocumentVotePollWinnerInfo('noWinner'); + expect(noWinner.isNoWinner()).to.be.true; + + const locked = new wasm.ContestedDocumentVotePollWinnerInfo('LOCKED'); + expect(locked.isLocked()).to.be.true; + + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const identity = new wasm.ContestedDocumentVotePollWinnerInfo('Identity', identityId); + expect(identity.isWonByIdentity()).to.be.true; + }); + }); + + describe('conversion methods', () => { + it('should round-trip NoWinner via toJSON/fromJSON', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('NoWinner'); + + const json = info.toJSON(); + // Simple enum variants serialize to strings in serde + expect(json).to.equal('NoWinner'); + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromJSON(json); + expect(restored.kind).to.equal(info.kind); + expect(restored.isNoWinner()).to.be.true; + }); + + it('should round-trip WonByIdentity via toJSON/fromJSON', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const info = new wasm.ContestedDocumentVotePollWinnerInfo('WonByIdentity', identityId); + + const json = info.toJSON(); + expect(json).to.be.an('object'); + // Serde serializes tuple variants as { VariantName: value } + expect(json.WonByIdentity).to.be.a('string'); + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromJSON(json); + expect(restored.kind).to.equal(info.kind); + expect(restored.identityId.toBase58()).to.equal(info.identityId.toBase58()); + }); + + it('should round-trip Locked via toJSON/fromJSON', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('Locked'); + + const json = info.toJSON(); + // Simple enum variants serialize to strings in serde + expect(json).to.equal('Locked'); + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromJSON(json); + expect(restored.kind).to.equal(info.kind); + expect(restored.isLocked()).to.be.true; + }); + + it('should round-trip NoWinner via toObject/fromObject', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('NoWinner'); + + const obj = info.toObject(); + // Simple enum variants serialize to strings in serde + expect(obj).to.equal('NoWinner'); + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromObject(obj); + expect(restored.kind).to.equal(info.kind); + }); + + it('should round-trip WonByIdentity via toObject/fromObject', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const info = new wasm.ContestedDocumentVotePollWinnerInfo('WonByIdentity', identityId); + + const obj = info.toObject(); + expect(obj).to.be.an('object'); + // Serde serializes tuple variants as { VariantName: value } + expect(obj.WonByIdentity).to.not.be.undefined; + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromObject(obj); + expect(restored.kind).to.equal(info.kind); + expect(restored.identityId.toBase58()).to.equal(info.identityId.toBase58()); + }); + + it('should round-trip Locked via toObject/fromObject', () => { + const info = new wasm.ContestedDocumentVotePollWinnerInfo('Locked'); + + const obj = info.toObject(); + // Simple enum variants serialize to strings in serde + expect(obj).to.equal('Locked'); + + const restored = wasm.ContestedDocumentVotePollWinnerInfo.fromObject(obj); + expect(restored.kind).to.equal(info.kind); + }); + }); +}); diff --git a/packages/wasm-dpp2/tests/unit/ContractBounds.spec.mjs b/packages/wasm-dpp2/tests/unit/ContractBounds.spec.mjs new file mode 100644 index 00000000000..19594b74e33 --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/ContractBounds.spec.mjs @@ -0,0 +1,110 @@ +import getWasm from './helpers/wasm.js'; + +let wasm; + +before(async () => { + wasm = await getWasm(); +}); + +describe('ContractBounds', () => { + const contractIdHex = '1111111111111111111111111111111111111111111111111111111111111111'; + + describe('constructor', () => { + it('should create SingleContract bounds without document type', () => { + const bounds = new wasm.ContractBounds( + Buffer.from(contractIdHex, 'hex'), + ); + + // contractBoundsType returns the serde string representation from rs-dpp + expect(bounds.contractBoundsType).to.equal('singleContract'); + expect(bounds.documentTypeName).to.be.null; + }); + + it('should create SingleContractDocumentType bounds with document type', () => { + const bounds = new wasm.ContractBounds( + Buffer.from(contractIdHex, 'hex'), + 'note', + ); + + // contractBoundsType returns the serde string representation from rs-dpp + expect(bounds.contractBoundsType).to.equal('documentType'); + expect(bounds.documentTypeName).to.equal('note'); + }); + }); + + describe('static constructors', () => { + it('should create SingleContract via static method', () => { + const bounds = wasm.ContractBounds.SingleContract( + Buffer.from(contractIdHex, 'hex'), + ); + + expect(bounds.contractBoundsType).to.equal('singleContract'); + }); + + it('should create SingleContractDocumentType via static method', () => { + const bounds = wasm.ContractBounds.SingleContractDocumentType( + Buffer.from(contractIdHex, 'hex'), + 'profile', + ); + + expect(bounds.contractBoundsType).to.equal('documentType'); + expect(bounds.documentTypeName).to.equal('profile'); + }); + }); + + describe('conversion methods', () => { + it('should round-trip SingleContract via toJSON/fromJSON', () => { + const bounds = wasm.ContractBounds.SingleContract( + Buffer.from(contractIdHex, 'hex'), + ); + + const json = bounds.toJSON(); + expect(json).to.be.an('object'); + expect(json.type).to.equal('singleContract'); + + const restored = wasm.ContractBounds.fromJSON(json); + expect(restored.contractBoundsType).to.equal(bounds.contractBoundsType); + expect(restored.identifier.toBase58()).to.equal(bounds.identifier.toBase58()); + }); + + it('should round-trip SingleContractDocumentType via toJSON/fromJSON', () => { + const bounds = wasm.ContractBounds.SingleContractDocumentType( + Buffer.from(contractIdHex, 'hex'), + 'profile', + ); + + const json = bounds.toJSON(); + expect(json).to.be.an('object'); + expect(json.type).to.equal('documentType'); + + const restored = wasm.ContractBounds.fromJSON(json); + expect(restored.contractBoundsType).to.equal(bounds.contractBoundsType); + expect(restored.documentTypeName).to.equal(bounds.documentTypeName); + expect(restored.identifier.toBase58()).to.equal(bounds.identifier.toBase58()); + }); + }); + + describe('getters', () => { + it('should return identifier', () => { + const bounds = new wasm.ContractBounds( + Buffer.from(contractIdHex, 'hex'), + ); + + expect(bounds.identifier).to.be.an('object'); + expect(bounds.identifier.__type).to.equal('Identifier'); + }); + + it('should return contractBoundsTypeNumber', () => { + const singleContract = wasm.ContractBounds.SingleContract( + Buffer.from(contractIdHex, 'hex'), + ); + expect(singleContract.contractBoundsTypeNumber).to.equal(0); + + const documentType = wasm.ContractBounds.SingleContractDocumentType( + Buffer.from(contractIdHex, 'hex'), + 'note', + ); + expect(documentType.contractBoundsTypeNumber).to.equal(1); + }); + }); +}); diff --git a/packages/wasm-dpp2/tests/unit/CoreScript.spec.mjs b/packages/wasm-dpp2/tests/unit/CoreScript.spec.mjs index e0ea5e46191..8eedbfde6a1 100644 --- a/packages/wasm-dpp2/tests/unit/CoreScript.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/CoreScript.spec.mjs @@ -29,7 +29,7 @@ describe('CoreScript', () => { it('should allow to convert to asm P2PKH', () => { const script = wasm.CoreScript.newP2PKH([195, 219, 253, 64, 231, 248, 164, 132, 92, 47, 142, 134, 138, 22, 124, 152, 64, 73, 118, 73]); - expect(script.ASMString()).to.equal('OP_DUP OP_HASH160 OP_PUSHBYTES_20 c3dbfd40e7f8a4845c2f8e868a167c9840497649 OP_EQUALVERIFY OP_CHECKSIG'); + expect(script.toASMString()).to.equal('OP_DUP OP_HASH160 OP_PUSHBYTES_20 c3dbfd40e7f8a4845c2f8e868a167c9840497649 OP_EQUALVERIFY OP_CHECKSIG'); }); it('should allow to convert to adddress', () => { @@ -37,5 +37,14 @@ describe('CoreScript', () => { expect(script.toAddress(wasm.Network.Testnet)).to.equal('yQW6TmUFef5CDyhEYwjoN8aUTMmKLYYNDm'); }); + + it('should allow to get base64 representation', () => { + const script = wasm.CoreScript.fromBytes(Buffer.from('76a9142de40f87177f6e167fb9fcda9a3b3c64fc42468f88ac', 'hex')); + + const base64 = script.toBase64(); + const bytes = script.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + }); }); }); diff --git a/packages/wasm-dpp2/tests/unit/DataContract.spec.mjs b/packages/wasm-dpp2/tests/unit/DataContract.spec.mjs index 8812cf9088c..ca7ebcd4423 100644 --- a/packages/wasm-dpp2/tests/unit/DataContract.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/DataContract.spec.mjs @@ -1,5 +1,5 @@ import getWasm from './helpers/wasm.js'; -import { value, id, ownerId } from './mocks/DataContract/index.js'; +import { json, object, id, ownerId } from './mocks/DataContract/index.js'; import { fromHexString } from './utils/hex.js'; let wasm; @@ -19,23 +19,25 @@ describe('DataContract', () => { describe('serialization / deserialization', () => { it('should allows to create DataContract from schema without full validation', () => { - const identifier = new wasm.Identifier(value.ownerId); + const identifier = new wasm.Identifier(object.ownerId); - const dataContract = new wasm.DataContract(identifier, BigInt(2), value.documentSchemas, null, false); + // Constructor accepts object format (with BigInt for integers) + const dataContract = new wasm.DataContract(identifier, BigInt(2), object.documentSchemas, null, false); expect(dataContract.__wbg_ptr).to.not.equal(0); }); it('should allows to create DataContract from schema with full validation', () => { - const identifier = new wasm.Identifier(value.ownerId); + const identifier = new wasm.Identifier(object.ownerId); - const dataContract = new wasm.DataContract(identifier, BigInt(2), value.documentSchemas, null, true); + // Constructor accepts object format (with BigInt for integers) + const dataContract = new wasm.DataContract(identifier, BigInt(2), object.documentSchemas, null, true); expect(dataContract.__wbg_ptr).to.not.equal(0); }); it('should allows to create DataContract from value with full validation and without platform version', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); expect(dataContract.__wbg_ptr).to.not.equal(0); }); @@ -43,7 +45,7 @@ describe('DataContract', () => { it('should allows to convert DataContract to bytes and from bytes', () => { const [dataContractBytes] = dataContractsBytes; - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); expect(dataContract.toBytes()).to.deep.equal(fromHexString(dataContractBytes)); @@ -58,78 +60,111 @@ describe('DataContract', () => { const [dataContractBytes] = dataContractsBytes; const dataContractFromBytes = wasm.DataContract.fromBytes(fromHexString(dataContractBytes), false, PlatformVersion.PLATFORM_V1); - const dataContractFromValue = wasm.DataContract.fromValue(value, true); + const dataContractFromValue = wasm.DataContract.fromJSON(json, true); - expect(dataContractFromBytes.toValue()).to.deep.equal(dataContractFromValue.toValue()); + expect(dataContractFromBytes.toObject()).to.deep.equal(dataContractFromValue.toObject()); }); it('should allows to create DataContract from bytes with full validation and without version', () => { const [dataContractBytes] = dataContractsBytes; const dataContractFromBytes = wasm.DataContract.fromBytes(fromHexString(dataContractBytes), true); - const dataContractFromValue = wasm.DataContract.fromValue(value, true); + const dataContractFromValue = wasm.DataContract.fromJSON(json, true); - expect(dataContractFromBytes.toValue()).to.deep.equal(dataContractFromValue.toValue()); + expect(dataContractFromBytes.toObject()).to.deep.equal(dataContractFromValue.toObject()); + }); + + it('should allow to create DataContract from object produced by toObject', () => { + const originalContract = wasm.DataContract.fromJSON(json, true); + const objectRepresentation = originalContract.toObject(PlatformVersion.PLATFORM_V1); + + const reconstructedContract = wasm.DataContract.fromObject( + objectRepresentation, + true, + PlatformVersion.PLATFORM_V1, + ); + + expect(reconstructedContract.toObject(PlatformVersion.PLATFORM_V1)).to.deep.equal(objectRepresentation); + }); + + it('should allow to convert DataContract to base64 and from base64', () => { + const [dataContractBytes] = dataContractsBytes; + + const dataContract = wasm.DataContract.fromBytes(fromHexString(dataContractBytes), true, PlatformVersion.PLATFORM_V1); + + const base64 = dataContract.toBase64(PlatformVersion.PLATFORM_V1); + const bytes = dataContract.toBytes(PlatformVersion.PLATFORM_V1); + const bytesFromBase64 = Buffer.from(base64, 'base64'); + + expect(bytesFromBase64).to.deep.equal(Buffer.from(bytes)); + + const dataContractFromBase64 = wasm.DataContract.fromBase64(base64, true, PlatformVersion.PLATFORM_V1); + + expect(Buffer.from(dataContractFromBase64.toBytes(PlatformVersion.PLATFORM_V1))).to.deep.equal(Buffer.from(bytes)); }); it('should allow to get json', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); - expect(dataContract.toJSON()).to.deep.equal(value); + expect(dataContract.toJSON()).to.deep.equal(json); }); }); describe('getters', () => { it('should allow to get schemas', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); - expect(dataContract.getSchemas()).to.deep.equal(value.documentSchemas); + const schemas = dataContract.getSchemas(); + + expect(schemas).to.deep.equal(object.documentSchemas); }); it('should allow to get version', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); - expect(dataContract.version).to.deep.equal(value.version); + expect(dataContract.version).to.deep.equal(json.version); }); it('should allow to get id', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); - expect(dataContract.id.base58()).to.deep.equal(id); + expect(dataContract.id.toBase58()).to.deep.equal(id); }); it('should allow to get owner id', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); - expect(dataContract.ownerId.base58()).to.deep.equal(ownerId); + expect(dataContract.ownerId.toBase58()).to.deep.equal(ownerId); }); it('should allow to get config', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); + + const config = dataContract.getConfig(); - expect(dataContract.getConfig()).to.deep.equal(value.config); + expect(config).to.deep.equal(object.config); }); }); describe('setters', () => { it('should allow to set id', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); dataContract.id = new wasm.Identifier('7ckT6Y19HnjfqoPFmfL995i4z2HwgZ8UttNmP99LtCBH'); - expect(dataContract.id.base58()).to.deep.equal('7ckT6Y19HnjfqoPFmfL995i4z2HwgZ8UttNmP99LtCBH'); + expect(dataContract.id.toBase58()).to.deep.equal('7ckT6Y19HnjfqoPFmfL995i4z2HwgZ8UttNmP99LtCBH'); }); it('should allow to set owner id', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); dataContract.ownerId = new wasm.Identifier('3bx13Wd5k4LwHAvXJrayc5HdKPyiccKWYECPQGGYfnVL'); - expect(dataContract.ownerId.base58()).to.deep.equal('3bx13Wd5k4LwHAvXJrayc5HdKPyiccKWYECPQGGYfnVL'); + expect(dataContract.ownerId.toBase58()).to.deep.equal('3bx13Wd5k4LwHAvXJrayc5HdKPyiccKWYECPQGGYfnVL'); }); it('should allow to set version', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); dataContract.version = 20; @@ -137,7 +172,7 @@ describe('DataContract', () => { }); it('should allow to set config', () => { - const dataContract = wasm.DataContract.fromValue(value, true); + const dataContract = wasm.DataContract.fromJSON(json, true); const oldConfig = dataContract.getConfig(); @@ -149,17 +184,23 @@ describe('DataContract', () => { }); it('should allow to set schema', () => { - const dataContract = wasm.DataContract.fromValue(value, true); - - const oldSchema = dataContract.getSchemas(); + const dataContract = wasm.DataContract.fromJSON(json, true); + // Use JSON fixture for setting (numbers, not BigInt) const newSchema = { - pupup: oldSchema.withdrawal, + pupup: json.documentSchemas.withdrawal, }; dataContract.setSchemas(newSchema); - expect(dataContract.getSchemas()).to.deep.equal(newSchema); + const schemas = dataContract.getSchemas(); + + // getSchemas returns object format (BigInt), so compare with object fixture + const expectedSchema = { + pupup: object.documentSchemas.withdrawal, + }; + + expect(schemas).to.deep.equal(expectedSchema); }); }); @@ -169,7 +210,7 @@ describe('DataContract', () => { const generatedId = wasm.DataContract.generateId(identifier, BigInt(4)); - expect(generatedId.base58()).to.deep.equal('7ckT6Y19HnjfqoPFmfL995i4z2HwgZ8UttNmP99LtCBH'); + expect(generatedId.toBase58()).to.deep.equal('7ckT6Y19HnjfqoPFmfL995i4z2HwgZ8UttNmP99LtCBH'); }); }); }); diff --git a/packages/wasm-dpp2/tests/unit/DataContractCreateStateTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/DataContractCreateStateTransition.spec.mjs index ac6ed7a9409..e0e24974434 100644 --- a/packages/wasm-dpp2/tests/unit/DataContractCreateStateTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/DataContractCreateStateTransition.spec.mjs @@ -13,7 +13,7 @@ before(async () => { describe('DataContract Create Transition', () => { describe('serialization / deserialization', () => { it('should allow to create document_transitions from data contract', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -22,7 +22,7 @@ describe('DataContract Create Transition', () => { }); it('should allow to convert document_transitions to bytes and create from bytes', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -37,7 +37,7 @@ describe('DataContract Create Transition', () => { }); it('should allow to convert data contract transition to state document_transitions and create data contract transition from state transition', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -51,7 +51,7 @@ describe('DataContract Create Transition', () => { describe('getters', () => { it('should allow to get feature version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -59,7 +59,7 @@ describe('DataContract Create Transition', () => { }); it('should allow to verify protocol version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -67,7 +67,7 @@ describe('DataContract Create Transition', () => { }); it('should allow to verify incorrect protocol version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -80,7 +80,7 @@ describe('DataContract Create Transition', () => { }); it('should allow to get data contract', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); @@ -94,7 +94,7 @@ describe('DataContract Create Transition', () => { it('should allow to set the data contract', () => { const [dataContractBytes] = dataContractsBytes; - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractCreateTransition(dataContract, BigInt(1)); diff --git a/packages/wasm-dpp2/tests/unit/DataContractUpdateStateTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/DataContractUpdateStateTransition.spec.mjs index bdca18946fd..615406ac095 100644 --- a/packages/wasm-dpp2/tests/unit/DataContractUpdateStateTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/DataContractUpdateStateTransition.spec.mjs @@ -13,7 +13,7 @@ before(async () => { describe('DataContract Update Transition', () => { describe('serialization / deserialization', () => { it('should allow to create document_transitions from data contract', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -22,7 +22,7 @@ describe('DataContract Update Transition', () => { }); it('should allow to convert document_transitions to bytes and create from bytes', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -37,7 +37,7 @@ describe('DataContract Update Transition', () => { }); it('should allow to convert data contract transition to state document_transitions and create data contract transition from state transition', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -51,7 +51,7 @@ describe('DataContract Update Transition', () => { describe('getters', () => { it('should allow to get feature version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -59,7 +59,7 @@ describe('DataContract Update Transition', () => { }); it('should allow to verify protocol version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -67,7 +67,7 @@ describe('DataContract Update Transition', () => { }); it('should allow to verify incorrect protocol version', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -80,7 +80,7 @@ describe('DataContract Update Transition', () => { }); it('should allow to get data contract', () => { - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); @@ -94,7 +94,7 @@ describe('DataContract Update Transition', () => { it('should allow to set the data contract', () => { const [dataContractBytes] = dataContractsBytes; - const dataContract = wasm.DataContract.fromValue(value, false, PlatformVersion.PLATFORM_V1); + const dataContract = wasm.DataContract.fromJSON(value, false, PlatformVersion.PLATFORM_V1); const dataContractTransition = new wasm.DataContractUpdateTransition(dataContract, BigInt(1)); diff --git a/packages/wasm-dpp2/tests/unit/Document.spec.mjs b/packages/wasm-dpp2/tests/unit/Document.spec.mjs index 7509a26e37f..5c56086fc1b 100644 --- a/packages/wasm-dpp2/tests/unit/Document.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/Document.spec.mjs @@ -34,34 +34,89 @@ describe('Document', () => { }); it('should allows to create Document from bytes and convert to bytes', () => { - const dataContract = wasm.DataContract.fromValue(dataContractValue, false); + const dataContract = wasm.DataContract.fromJSON(dataContractValue, false); const documentInstance = wasm.Document.fromBytes(fromHexString(documentBytes), dataContract, 'note'); - const bytes = documentInstance.bytes(dataContract, PlatformVersion.PLATFORM_V1); + const bytes = documentInstance.toBytes(dataContract, PlatformVersion.PLATFORM_V1); - expect(documentInstance.dataContractId.base58()).to.equal(dataContract.id.base58()); + expect(documentInstance.dataContractId.toBase58()).to.equal(dataContract.id.toBase58()); expect(bytes).to.deep.equal(fromHexString(documentBytes)); expect(dataContract.__wbg_ptr).to.not.equal(0); }); }); + describe('toObject / fromObject', () => { + it('should convert to object with binary fields as Uint8Array', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + + const obj = documentInstance.toObject(); + + expect(obj.$id).to.be.instanceOf(Uint8Array); + expect(obj.$ownerId).to.be.instanceOf(Uint8Array); + expect(obj.$dataContractId).to.be.instanceOf(Uint8Array); + expect(obj.$type).to.equal(documentTypeName); + // toObject uses BigInt for u64 values like revision to preserve precision + expect(BigInt(obj.$revision)).to.equal(revision); + }); + + it('should roundtrip through toObject / fromObject', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + + const obj = documentInstance.toObject(); + const restored = wasm.Document.fromObject(obj); + + expect(restored.id.toBase58()).to.equal(documentInstance.id.toBase58()); + expect(restored.ownerId.toBase58()).to.equal(documentInstance.ownerId.toBase58()); + expect(restored.dataContractId.toBase58()).to.equal(documentInstance.dataContractId.toBase58()); + expect(restored.documentTypeName).to.equal(documentInstance.documentTypeName); + expect(restored.revision).to.equal(documentInstance.revision); + }); + }); + + describe('toJSON / fromJSON', () => { + it('should convert to JSON with identifiers as Base58 strings', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + + const json = documentInstance.toJSON(); + + expect(typeof json.$id).to.equal('string'); + expect(typeof json.$ownerId).to.equal('string'); + expect(typeof json.$dataContractId).to.equal('string'); + expect(json.$type).to.equal(documentTypeName); + expect(json.$revision).to.equal(Number(revision)); + }); + + it('should roundtrip through toJSON / fromJSON', () => { + const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); + + const json = documentInstance.toJSON(); + const restored = wasm.Document.fromJSON(json); + + expect(restored.id.toBase58()).to.equal(documentInstance.id.toBase58()); + expect(restored.ownerId.toBase58()).to.equal(documentInstance.ownerId.toBase58()); + expect(restored.dataContractId.toBase58()).to.equal(documentInstance.dataContractId.toBase58()); + expect(restored.documentTypeName).to.equal(documentInstance.documentTypeName); + expect(restored.revision).to.equal(documentInstance.revision); + }); + }); + describe('getters', () => { it('should return document id', () => { const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); - expect(documentInstance.id.base58()).to.deep.equal(id); + expect(documentInstance.id.toBase58()).to.deep.equal(id); }); it('should return owner id', () => { const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); - expect(documentInstance.ownerId.base58()).to.deep.equal(ownerId); + expect(documentInstance.ownerId.toBase58()).to.deep.equal(ownerId); }); it('should return data contract id', () => { const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); - expect(documentInstance.dataContractId.base58()).to.deep.equal(dataContractId); + expect(documentInstance.dataContractId.toBase58()).to.deep.equal(dataContractId); }); it('should return properties', () => { @@ -83,7 +138,7 @@ describe('Document', () => { documentInstance.id = ownerId; - expect(documentInstance.id.base58()).to.deep.equal(ownerId); + expect(documentInstance.id.toBase58()).to.deep.equal(ownerId); }); it('should allow to set document owner id', () => { @@ -91,7 +146,7 @@ describe('Document', () => { documentInstance.ownerId = id; - expect(documentInstance.ownerId.base58()).to.deep.equal(id); + expect(documentInstance.ownerId.toBase58()).to.deep.equal(id); }); it('should allow to set entropy', () => { diff --git a/packages/wasm-dpp2/tests/unit/DocumentTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/DocumentTransition.spec.mjs index bdaef71751b..7ca9b9f7afe 100644 --- a/packages/wasm-dpp2/tests/unit/DocumentTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/DocumentTransition.spec.mjs @@ -38,13 +38,13 @@ describe('DocumentTransition', () => { it('should allow to get dataContractId', () => { const documentTransition = createTransition.toDocumentTransition(); - expect(documentTransition.dataContractId.base58()).to.deep.equal(documentInstance.dataContractId.base58()); + expect(documentTransition.dataContractId.toBase58()).to.deep.equal(documentInstance.dataContractId.toBase58()); }); it('should allow to get id', () => { const documentTransition = createTransition.toDocumentTransition(); - expect(documentTransition.id.base58()).to.deep.equal(documentInstance.id.base58()); + expect(documentTransition.id.toBase58()).to.deep.equal(documentInstance.id.toBase58()); }); it('should allow to get documentTypeName', () => { diff --git a/packages/wasm-dpp2/tests/unit/DocumentsTransitions.spec.mjs b/packages/wasm-dpp2/tests/unit/DocumentsTransitions.spec.mjs index d99aa0264c6..f6275eb1790 100644 --- a/packages/wasm-dpp2/tests/unit/DocumentsTransitions.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/DocumentsTransitions.spec.mjs @@ -37,7 +37,7 @@ describe('DocumentsTransitions', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(createTransition.__wbg_ptr).to.not.equal(0); @@ -51,7 +51,7 @@ describe('DocumentsTransitions', () => { const documentTransition = createTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -99,7 +99,7 @@ describe('DocumentsTransitions', () => { const documentTransition = deleteTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(deleteTransition.__wbg_ptr).to.not.equal(0); @@ -113,7 +113,7 @@ describe('DocumentsTransitions', () => { const documentTransition = deleteTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -161,7 +161,7 @@ describe('DocumentsTransitions', () => { const documentTransition = replaceTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(replaceTransition.__wbg_ptr).to.not.equal(0); @@ -175,7 +175,7 @@ describe('DocumentsTransitions', () => { const documentTransition = replaceTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -223,7 +223,7 @@ describe('DocumentsTransitions', () => { const documentTransition = transferTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(transferTransition.__wbg_ptr).to.not.equal(0); @@ -237,7 +237,7 @@ describe('DocumentsTransitions', () => { const documentTransition = transferTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -285,7 +285,7 @@ describe('DocumentsTransitions', () => { const documentTransition = updatePriceTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(updatePriceTransition.__wbg_ptr).to.not.equal(0); @@ -299,7 +299,7 @@ describe('DocumentsTransitions', () => { const documentTransition = updatePriceTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -347,7 +347,7 @@ describe('DocumentsTransitions', () => { const documentTransition = purchaseTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); expect(documentInstance.__wbg_ptr).to.not.equal(0); expect(purchaseTransition.__wbg_ptr).to.not.equal(0); @@ -361,7 +361,7 @@ describe('DocumentsTransitions', () => { const documentTransition = purchaseTransition.toDocumentTransition(); - const batchTransition = wasm.BatchTransition.fromV0Transitions([documentTransition, documentTransition], documentInstance.ownerId, 1, 1); + const batchTransition = wasm.BatchTransition.fromBatchedTransitions([new wasm.BatchedTransition(documentTransition), new wasm.BatchedTransition(documentTransition)], documentInstance.ownerId, 1); const st = batchTransition.toStateTransition(); @@ -458,7 +458,7 @@ describe('DocumentsTransitions', () => { const documentInstance = new wasm.Document(document, documentTypeName, revision, dataContractId, ownerId, id); const transferTransition = new wasm.DocumentTransferTransition(documentInstance, BigInt(1), documentInstance.ownerId); - expect(transferTransition.recipientId.base58()).to.deep.equal(documentInstance.ownerId.base58()); + expect(transferTransition.recipientId.toBase58()).to.deep.equal(documentInstance.ownerId.toBase58()); }); }); diff --git a/packages/wasm-dpp2/tests/unit/Group.spec.mjs b/packages/wasm-dpp2/tests/unit/Group.spec.mjs new file mode 100644 index 00000000000..f21a51bb456 --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/Group.spec.mjs @@ -0,0 +1,141 @@ +import getWasm from './helpers/wasm.js'; + +let wasm; + +before(async () => { + wasm = await getWasm(); +}); + +describe('Group', () => { + const memberIdHex = '1111111111111111111111111111111111111111111111111111111111111111'; + const member2IdHex = '2222222222222222222222222222222222222222222222222222222222222222'; + + describe('constructor', () => { + it('should create Group with members and required power', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + expect(group.requiredPower).to.equal(50); + expect(group.members).to.be.an('object'); + }); + + it('should create Group with multiple members', () => { + const memberId1 = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const memberId2 = wasm.Identifier.fromBytes(Buffer.from(member2IdHex, 'hex')); + const members = { + [memberId1.toBase58()]: 100, + [memberId2.toBase58()]: 50, + }; + + const group = new wasm.Group(members, 75); + + expect(group.requiredPower).to.equal(75); + }); + }); + + describe('getters and setters', () => { + it('should get and set members', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + const fetchedMembers = group.members; + expect(fetchedMembers).to.be.an('object'); + expect(fetchedMembers[memberId.toBase58()]).to.equal(100); + }); + + it('should get and set required power', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + expect(group.requiredPower).to.equal(50); + + group.requiredPower = 75; + expect(group.requiredPower).to.equal(75); + }); + + it('should set member required power', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + group.setMemberRequiredPower(memberId, 200); + + const updatedMembers = group.members; + expect(updatedMembers[memberId.toBase58()]).to.equal(200); + }); + }); + + describe('conversion methods', () => { + it('should round-trip via toJSON/fromJSON', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + const json = group.toJSON(); + expect(json).to.be.an('object'); + + const restored = wasm.Group.fromJSON(json); + expect(restored.requiredPower).to.equal(group.requiredPower); + }); + + it('should export toObject', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + const obj = group.toObject(); + // toObject exports as Map type in serde_wasm_bindgen which doesn't round-trip + // but it should at least be defined + expect(obj).to.not.be.undefined; + }); + + it('should round-trip Group with multiple members via toJSON/fromJSON', () => { + const memberId1 = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const memberId2 = wasm.Identifier.fromBytes(Buffer.from(member2IdHex, 'hex')); + const members = { + [memberId1.toBase58()]: 100, + [memberId2.toBase58()]: 50, + }; + + const group = new wasm.Group(members, 75); + + const json = group.toJSON(); + const restored = wasm.Group.fromJSON(json); + + expect(restored.requiredPower).to.equal(75); + }); + }); + + describe('type properties', () => { + it('should return correct __type', () => { + const memberId = wasm.Identifier.fromBytes(Buffer.from(memberIdHex, 'hex')); + const members = { + [memberId.toBase58()]: 100, + }; + + const group = new wasm.Group(members, 50); + + expect(group.__type).to.equal('Group'); + }); + }); +}); diff --git a/packages/wasm-dpp2/tests/unit/Identifier.spec.mjs b/packages/wasm-dpp2/tests/unit/Identifier.spec.mjs index 55019951ba4..62922673761 100644 --- a/packages/wasm-dpp2/tests/unit/Identifier.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/Identifier.spec.mjs @@ -62,13 +62,13 @@ describe('Identifier', () => { it('should allow to get identifier base58', () => { const identifier = wasm.Identifier.fromBase58('ckBqfQe7LU7vwrwXopyCB4n5phZShjA16BGhNGpsD5U'); - expect(identifier.base58()).to.equal('ckBqfQe7LU7vwrwXopyCB4n5phZShjA16BGhNGpsD5U'); + expect(identifier.toBase58()).to.equal('ckBqfQe7LU7vwrwXopyCB4n5phZShjA16BGhNGpsD5U'); }); it('should allow to get identifier base64', () => { const identifier = wasm.Identifier.fromBase58('ckBqfQe7LU7vwrwXopyCB4n5phZShjA16BGhNGpsD5U'); - expect(identifier.base64()).to.equal('CSgo7cCB07oaVPBDJZuUE2jyxxiIGwap00eIOyG/4xM='); + expect(identifier.toBase64()).to.equal('CSgo7cCB07oaVPBDJZuUE2jyxxiIGwap00eIOyG/4xM='); }); it('should allow to get identifier hex', () => { diff --git a/packages/wasm-dpp2/tests/unit/Identity.spec.mjs b/packages/wasm-dpp2/tests/unit/Identity.spec.mjs index 8f8fe2fd0d0..247f2849b98 100644 --- a/packages/wasm-dpp2/tests/unit/Identity.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/Identity.spec.mjs @@ -30,6 +30,34 @@ describe('Identity', () => { expect(identity.__wbg_ptr).to.not.equal(0); expect(newIdentity.__wbg_ptr).to.not.equal(0); }); + + it('should recreate identity from JSON output', () => { + const identity = new wasm.Identity(identifier); + const identityJson = identity.toJSON(); + + const restoredIdentity = wasm.Identity.fromJSON(identityJson); + + expect(Array.from(restoredIdentity.toBytes())).to.deep.equal(Array.from(identity.toBytes())); + }); + + it('should recreate identity from object output', () => { + const identity = new wasm.Identity(identifier); + const identityObject = identity.toObject(); + + // toObject returns plain JS values (Uint8Array for id, not Identifier instance) + expect(identityObject.id.constructor.name).to.equal('Uint8Array'); + expect(identityObject.id.length).to.equal(32); + expect(Array.isArray(identityObject.publicKeys)).to.equal(true); + expect(identityObject.balance).to.equal(BigInt(0)); + expect(identityObject.revision).to.equal(BigInt(0)); + + const restoredIdentity = wasm.Identity.fromObject(identityObject); + + expect(Array.from(restoredIdentity.toBytes())).to.deep.equal(Array.from(identity.toBytes())); + expect(restoredIdentity.id.toBytes()).to.deep.equal(identity.id.toBytes()); + expect(restoredIdentity.getPublicKeys().length).to.equal(identity.getPublicKeys().length); + }); + }); describe('getters', () => { diff --git a/packages/wasm-dpp2/tests/unit/IdentityCreateTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/IdentityCreateTransition.spec.mjs index 70bce6ea269..0aac24b94ca 100644 --- a/packages/wasm-dpp2/tests/unit/IdentityCreateTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/IdentityCreateTransition.spec.mjs @@ -28,6 +28,47 @@ describe('IdentityCreateTransition', () => { expect(transition.__wbg_ptr).to.not.equal(0); }); + + it('should serialize to JSON', () => { + const transition = wasm.IdentityCreateTransition.default(1); + + const json = transition.toJSON(); + // JSON uses camelCase (serde rename_all) + expect(json).to.have.property('publicKeys'); + expect(json).to.have.property('assetLockProof'); + expect(json).to.have.property('userFeeIncrease'); + expect(json).to.have.property('signature'); + expect(json.userFeeIncrease).to.equal(0); + }); + + it('should serialize to object', () => { + const transition = wasm.IdentityCreateTransition.default(1); + + const obj = transition.toObject(); + expect(obj).to.have.property('publicKeys'); + expect(obj).to.have.property('assetLockProof'); + expect(obj).to.have.property('userFeeIncrease'); + expect(obj).to.have.property('signature'); + }); + + it('should serialize to hex and base64', () => { + const transition = wasm.IdentityCreateTransition.default(1); + + const hex = transition.toHex(); + expect(hex).to.be.a('string'); + expect(hex.length).to.be.greaterThan(0); + + const base64 = transition.toBase64(); + expect(base64).to.be.a('string'); + expect(base64.length).to.be.greaterThan(0); + + // Verify they can be deserialized back + const fromHex = wasm.IdentityCreateTransition.fromHex(hex); + expect(fromHex.getIdentifier().toBase58()).to.equal(transition.getIdentifier().toBase58()); + + const fromBase64 = wasm.IdentityCreateTransition.fromBase64(base64); + expect(fromBase64.getIdentifier().toBase58()).to.equal(transition.getIdentifier().toBase58()); + }); }); describe('getters', () => { @@ -46,7 +87,7 @@ describe('IdentityCreateTransition', () => { it('should allow to get Identifier', () => { const transition = wasm.IdentityCreateTransition.default(1); - expect(transition.getIdentifier().base58()).to.equal('11111111111111111111111111111111'); + expect(transition.getIdentifier().toBase58()).to.equal('11111111111111111111111111111111'); }); it('should allow to get PublicKeys', () => { diff --git a/packages/wasm-dpp2/tests/unit/IdentityCreditTransferTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/IdentityCreditTransferTransition.spec.mjs index 9eefecf8141..9d9048d5302 100644 --- a/packages/wasm-dpp2/tests/unit/IdentityCreditTransferTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/IdentityCreditTransferTransition.spec.mjs @@ -24,19 +24,32 @@ describe('IdentityCreditTransferTransition', () => { expect(sender.__wbg_ptr).to.not.equal(0); expect(recipient.__wbg_ptr).to.not.equal(0); }); + + it('Should convert IdentityCreditTransferTransition to base64 and back', () => { + const transition = new wasm.IdentityCreditTransfer(BigInt(100), '11111111111111111111111111111111', 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', BigInt(199)); + + const base64 = transition.toBase64(); + const bytes = transition.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + + const restored = wasm.IdentityCreditTransfer.fromBase64(base64); + + expect(Buffer.from(restored.toBytes())).to.deep.equal(Buffer.from(bytes)); + }); }); describe('getters', () => { it('Should return recipientId', async () => { const transition = new wasm.IdentityCreditTransfer(BigInt(100), '11111111111111111111111111111111', 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', BigInt(199)); - expect(transition.recipientId.base58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); + expect(transition.recipientId.toBase58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); }); it('Should return senderId', async () => { const transition = new wasm.IdentityCreditTransfer(BigInt(100), '11111111111111111111111111111111', 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', BigInt(199)); - expect(transition.senderId.base58()).to.deep.equal('11111111111111111111111111111111'); + expect(transition.senderId.toBase58()).to.deep.equal('11111111111111111111111111111111'); }); it('Should return amount', async () => { @@ -78,11 +91,11 @@ describe('IdentityCreditTransferTransition', () => { transition.recipientId = recipient; - expect(transition.recipientId.base58()).to.deep.equal('11111111111111111111111111111111'); + expect(transition.recipientId.toBase58()).to.deep.equal('11111111111111111111111111111111'); transition.recipientId = 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'; - expect(transition.recipientId.base58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); + expect(transition.recipientId.toBase58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); expect(recipient.__wbg_ptr).to.not.equal(0); }); @@ -93,12 +106,12 @@ describe('IdentityCreditTransferTransition', () => { transition.senderId = sender; - expect(transition.senderId.base58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); + expect(transition.senderId.toBase58()).to.deep.equal('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); transition.senderId = '11111111111111111111111111111111'; expect(sender.__wbg_ptr).to.not.equal(0); - expect(transition.senderId.base58()).to.deep.equal('11111111111111111111111111111111'); + expect(transition.senderId.toBase58()).to.deep.equal('11111111111111111111111111111111'); }); it('Should return amount', async () => { diff --git a/packages/wasm-dpp2/tests/unit/IdentityCreditWithdrawalTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/IdentityCreditWithdrawalTransition.spec.mjs index b8c4a396a56..f575bde23e5 100644 --- a/packages/wasm-dpp2/tests/unit/IdentityCreditWithdrawalTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/IdentityCreditWithdrawalTransition.spec.mjs @@ -18,6 +18,22 @@ describe('IdentityCreditWithdrawalTransition', () => { expect(script.__wbg_ptr).to.not.equal(0); expect(transition.__wbg_ptr).to.not.equal(0); }); + + it('Should convert IdentityCreditWithdrawalTransition to base64 and back', () => { + const identifier = new wasm.Identifier('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); + const script = wasm.CoreScript.newP2PKH([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + + const transition = new wasm.IdentityCreditWithdrawalTransition(identifier, BigInt(111), 1, 'never', script, BigInt(1), 1); + + const base64 = transition.toBase64(); + const bytes = transition.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + + const restored = wasm.IdentityCreditWithdrawalTransition.fromBase64(base64); + + expect(Buffer.from(restored.toBytes())).to.deep.equal(Buffer.from(bytes)); + }); }); describe('getters', () => { @@ -45,7 +61,7 @@ describe('IdentityCreditWithdrawalTransition', () => { const transition = new wasm.IdentityCreditWithdrawalTransition(identifier, BigInt(111), 1, 'never', script, BigInt(1), 1); - expect(transition.identityId.base58()).to.deep.equal(identifier.base58()); + expect(transition.identityId.toBase58()).to.deep.equal(identifier.toBase58()); }); it('Should allow to get userFeeIncrease', () => { @@ -129,7 +145,7 @@ describe('IdentityCreditWithdrawalTransition', () => { transition.identityId = identifier2; - expect(transition.identityId.base58()).to.deep.equal(identifier2.base58()); + expect(transition.identityId.toBase58()).to.deep.equal(identifier2.toBase58()); }); it('Should allow to set userFeeIncrease', () => { diff --git a/packages/wasm-dpp2/tests/unit/IdentityTopUpTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/IdentityTopUpTransition.spec.mjs index 257247b80d0..92f88d61998 100644 --- a/packages/wasm-dpp2/tests/unit/IdentityTopUpTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/IdentityTopUpTransition.spec.mjs @@ -23,6 +23,25 @@ describe('IdentityTopUpTransition', () => { expect(transition.__wbg_ptr).to.not.equal(0); expect(assetLockProof.__wbg_ptr).to.not.equal(0); }); + + it('should convert IdentityTopUpTransition to base64 and back', () => { + const assetLockProof = wasm.AssetLockProof.createInstantAssetLockProof( + [1, 1, 193, 219, 244, 102, 77, 20, 251, 187, 201, 109, 168, 125, 7, 244, 118, 119, 210, 53, 238, 105, 138, 31, 7, 73, 30, 128, 131, 175, 114, 76, 187, 37, 0, 0, 0, 0, 177, 215, 65, 96, 204, 228, 86, 13, 14, 185, 46, 65, 241, 38, 226, 172, 59, 96, 158, 15, 126, 90, 225, 3, 140, 221, 96, 131, 254, 12, 236, 26, 48, 124, 31, 23, 184, 202, 122, 231, 123, 59, 43, 118, 27, 214, 225, 58, 44, 191, 232, 10, 33, 35, 8, 113, 145, 159, 158, 88, 80, 0, 0, 0, 173, 103, 128, 160, 29, 226, 161, 219, 167, 194, 158, 38, 19, 51, 143, 248, 161, 87, 126, 32, 143, 209, 152, 44, 174, 6, 25, 210, 101, 127, 131, 65, 202, 241, 47, 166, 132, 10, 199, 15, 187, 136, 11, 217, 237, 13, 173, 64, 11, 6, 112, 188, 234, 239, 204, 29, 3, 6, 35, 154, 44, 106, 44, 183, 171, 126, 146, 240, 153, 210, 187, 56, 133, 161, 11, 4, 151, 63, 89, 20, 44, 66, 153, 242, 97, 207, 44, 110, 208, 51, 198, 113, 104, 79, 154, 19], + [3, 0, 8, 0, 1, 193, 219, 244, 102, 77, 20, 251, 187, 201, 109, 168, 125, 7, 244, 118, 119, 210, 53, 238, 105, 138, 31, 7, 73, 30, 128, 131, 175, 114, 76, 187, 37, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 2, 146, 73, 163, 223, 124, 140, 225, 109, 126, 239, 87, 105, 184, 118, 87, 182, 100, 182, 6, 15, 200, 26, 44, 33, 215, 165, 110, 211, 232, 245, 233, 2, 32, 69, 161, 168, 128, 101, 26, 171, 20, 63, 30, 219, 87, 23, 4, 142, 115, 73, 73, 170, 203, 46, 187, 149, 32, 210, 171, 46, 136, 253, 188, 36, 12, 1, 33, 2, 144, 231, 28, 153, 30, 92, 52, 10, 111, 47, 107, 74, 225, 237, 151, 33, 188, 184, 247, 121, 70, 135, 174, 31, 160, 16, 216, 239, 225, 103, 88, 112, 255, 255, 255, 255, 2, 64, 66, 15, 0, 0, 0, 0, 0, 2, 106, 0, 216, 154, 230, 5, 0, 0, 0, 0, 25, 118, 169, 20, 229, 154, 45, 140, 145, 114, 18, 52, 205, 13, 179, 84, 94, 174, 149, 207, 101, 115, 51, 240, 136, 172, 0, 0, 0, 0, 36, 1, 1, 64, 66, 15, 0, 0, 0, 0, 0, 25, 118, 169, 20, 176, 213, 107, 82, 203, 147, 209, 128, 255, 63, 69, 219, 41, 250, 232, 254, 185, 168, 85, 184, 136, 172], + 0, + ); + + const transition = new wasm.IdentityTopUpTransition(assetLockProof, 'B7kcE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH', 11); + + const base64 = transition.toBase64(); + const bytes = transition.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + + const restored = wasm.IdentityTopUpTransition.fromBase64(base64); + + expect(Buffer.from(restored.toBytes())).to.deep.equal(Buffer.from(bytes)); + }); }); describe('getters', () => { @@ -53,7 +72,7 @@ describe('IdentityTopUpTransition', () => { const transition = new wasm.IdentityTopUpTransition(assetLockProof, 'B7kcE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH', 11); - expect(transition.identityIdentifier.base58()).to.deep.equal('B7kcE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH'); + expect(transition.identityIdentifier.toBase58()).to.deep.equal('B7kcE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH'); }); it('should allow to return signature', () => { @@ -106,7 +125,7 @@ describe('IdentityTopUpTransition', () => { transition.identityIdentifier = identifier; - expect(transition.identityIdentifier.base58()).to.deep.equal('777cE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH'); + expect(transition.identityIdentifier.toBase58()).to.deep.equal('777cE1juMBWEWkuYRJhVdAE2e6RaevrGxRsa1DrLCpQH'); }); it('should allow to set signature', () => { diff --git a/packages/wasm-dpp2/tests/unit/IdentityUpdateTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/IdentityUpdateTransition.spec.mjs index 87820e350fe..128fc59c4ef 100644 --- a/packages/wasm-dpp2/tests/unit/IdentityUpdateTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/IdentityUpdateTransition.spec.mjs @@ -22,6 +22,19 @@ describe('IdentityUpdateTransition', () => { expect(transition.__wbg_ptr).to.not.equal(0); expect(key.__wbg_ptr).to.not.equal(0); }); + + it('Should convert IdentityUpdateTransition to base64 and back', () => { + const transition = new wasm.IdentityUpdateTransition('GL2Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3', BigInt(1), BigInt(1), 1, [], []); + + const base64 = transition.toBase64(); + const bytes = transition.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + + const restored = wasm.IdentityUpdateTransition.fromBase64(base64); + + expect(Buffer.from(restored.toBytes())).to.deep.equal(Buffer.from(bytes)); + }); }); describe('getters', () => { @@ -40,7 +53,7 @@ describe('IdentityUpdateTransition', () => { it('Should return identityIdentifier', () => { const transition = new wasm.IdentityUpdateTransition('GL2Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3', BigInt(1), BigInt(1), 1, [], []); - expect(transition.identityIdentifier.base58()).to.deep.equal('GL2Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3'); + expect(transition.identityIdentifier.toBase58()).to.deep.equal('GL2Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3'); }); it('Should return publicKeyIdsToDisable', () => { @@ -82,7 +95,7 @@ describe('IdentityUpdateTransition', () => { transition.identityIdentifier = '11Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3'; - expect(transition.identityIdentifier.base58()).to.deep.equal('11Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3'); + expect(transition.identityIdentifier.toBase58()).to.deep.equal('11Rq8L3VuBEQfCAZykmUaiXXrsd1Bwub2gcaMmtNbn3'); }); it('Should allow to set revision', () => { diff --git a/packages/wasm-dpp2/tests/unit/InstantLockProof.spec.mjs b/packages/wasm-dpp2/tests/unit/InstantLockProof.spec.mjs index df6c3ce7a5b..bd3e233501d 100644 --- a/packages/wasm-dpp2/tests/unit/InstantLockProof.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/InstantLockProof.spec.mjs @@ -38,13 +38,51 @@ describe('InstantLock', () => { expect(instantLockProof.__wbg_ptr).to.not.equal(0); }); + + it('should round-trip via toJSON/fromJSON', () => { + const instantLockProof = new wasm.InstantAssetLockProof(instantLockBytes, transactionBytes, 0); + + const json = instantLockProof.toJSON(); + + // JSON format should have base64 strings + expect(json.instantLock).to.be.a('string'); + expect(json.transaction).to.be.a('string'); + expect(json.outputIndex).to.equal(0); + + // Verify base64 decodes to original bytes + expect(Buffer.from(json.instantLock, 'base64')).to.deep.equal(Buffer.from(instantLockBytes)); + expect(Buffer.from(json.transaction, 'base64')).to.deep.equal(Buffer.from(transactionBytes)); + + // Round-trip via fromJSON + const restored = wasm.InstantAssetLockProof.fromJSON(json); + + expect(restored.toObject()).to.deep.equal(instantLockProof.toObject()); + }); + + it('should round-trip via toObject/fromObject', () => { + const instantLockProof = new wasm.InstantAssetLockProof(instantLockBytes, transactionBytes, 0); + + const obj = instantLockProof.toObject(); + + // Object format should have Uint8Array + expect(obj.instantLock).to.be.instanceOf(Uint8Array); + expect(obj.transaction).to.be.instanceOf(Uint8Array); + expect(obj.outputIndex).to.equal(0); + + // Round-trip via fromObject + const restored = wasm.InstantAssetLockProof.fromObject(obj); + + expect(restored.toObject()).to.deep.equal(obj); + }); }); describe('getters', () => { - it('should allow to get output', () => { + it('should allow to get output as bytes', () => { const instantLockProof = new wasm.InstantAssetLockProof(instantLockBytes, transactionBytes, 0); - expect(instantLockProof.getOutput().constructor.name).to.deep.equal('TxOut'); + const output = instantLockProof.getOutput(); + expect(output).to.be.instanceOf(Uint8Array); + expect(output.length).to.be.greaterThan(0); }); it('should allow to convert to get OutPoint', () => { @@ -59,10 +97,11 @@ describe('InstantLock', () => { expect(instantLockProof.outputIndex).to.deep.equal(0); }); - it('should allow to get instant lock', () => { + it('should allow to get instant lock as bytes', () => { const instantLockProof = new wasm.InstantAssetLockProof(instantLockBytes, transactionBytes, 0); - expect(instantLockProof.instantLock.constructor.name).to.deep.equal('InstantLock'); + expect(instantLockProof.instantLock).to.be.instanceOf(Uint8Array); + expect(instantLockProof.instantLock).to.deep.equal(instantLockBytes); }); }); @@ -75,21 +114,13 @@ describe('InstantLock', () => { expect(instantLockProof.outputIndex).to.deep.equal(12); }); - it('should allow to set instant lock', () => { + it('should allow to set instant lock from bytes', () => { const instantLockProof = new wasm.InstantAssetLockProof(instantLockBytes, transactionBytes, 0); - const newInstantLockProof = new wasm.InstantLock( - 0, - [], - 'dbdb604952d08184b55d48c915ed78aadc81dbc5cc98e8b4821abe5b4bbcbecb', - '00000000000000151e0fe3ab9a12c57402153c9f476236148364ec4337213101', - 'a9f131626c49a2f183b7a2f563ad1dc50ac8220190dbedb805209b608eb864e01d62f18bc9faa60a8b8a27f5a0c7c8b914fa3a14360a2f25558ee0e0a693b18faccbb59ec39b9b3cae430e0b76eb080752ce103df76537a1a583680a5914529d', - ); - - instantLockProof.instantLock = newInstantLockProof; + // Set and verify the instant lock bytes round-trip + instantLockProof.instantLock = instantLockBytes; - expect(instantLockProof.instantLock.version).to.deep.equal(0); - expect(newInstantLockProof.__wbg_ptr).to.not.equal(0); + expect(instantLockProof.instantLock).to.deep.equal(instantLockBytes); }); }); }); diff --git a/packages/wasm-dpp2/tests/unit/OutPoint.spec.mjs b/packages/wasm-dpp2/tests/unit/OutPoint.spec.mjs index 2b3c55d8066..41d0ab37f8b 100644 --- a/packages/wasm-dpp2/tests/unit/OutPoint.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/OutPoint.spec.mjs @@ -49,5 +49,15 @@ describe('OutPoint', () => { expect(outpoint.toBytes()).to.deep.equal(Uint8Array.from(bytes)); }); + + it('should allow to get base64 representation', () => { + const outpoint = new wasm.OutPoint('e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d', 1); + + const base64 = outpoint.toBase64(); + const bytes = outpoint.toBytes(); + + expect(Buffer.from(base64, 'base64')).to.deep.equal(Buffer.from(bytes)); + expect(Buffer.from(wasm.OutPoint.fromBase64(base64).toBytes())).to.deep.equal(Buffer.from(bytes)); + }); }); }); diff --git a/packages/wasm-dpp2/tests/unit/PublicKeyInCreation.spec.mjs b/packages/wasm-dpp2/tests/unit/PublicKeyInCreation.spec.mjs index f74f87a7bc2..45dcf5e153a 100644 --- a/packages/wasm-dpp2/tests/unit/PublicKeyInCreation.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/PublicKeyInCreation.spec.mjs @@ -6,7 +6,7 @@ before(async () => { wasm = await getWasm(); }); -describe('InstantLock', () => { +describe('IdentityPublicKeyInCreation', () => { describe('serialization / deserialization', () => { it('should allow to create from values', () => { const publicKeyInCreation = new wasm.IdentityPublicKeyInCreation( @@ -253,4 +253,51 @@ describe('InstantLock', () => { expect([...publicKeyInCreation.signature]).to.deep.equal([1, 2, 3, 4, 5, 6]); }); }); + + describe('conversion methods', () => { + it('should round-trip via toJSON/fromJSON', () => { + const publicKeyInCreation = new wasm.IdentityPublicKeyInCreation( + 0, + 'AUTHENTICATION', + 'master', + 'ECDSA_SECP256K1', + false, + Buffer.from('0333d5cf3674001d2f64c55617b7b11a2e8fc62aab09708b49355e30c7205bdb2e', 'hex'), + [], + ); + + const json = publicKeyInCreation.toJSON(); + expect(json).to.be.an('object'); + expect(json.id).to.equal(0); + expect(json.purpose).to.equal(0); // AUTHENTICATION = 0 + expect(json.securityLevel).to.equal(0); // MASTER = 0 + expect(json.type).to.equal(0); // ECDSA_SECP256K1 = 0 + expect(json.readOnly).to.equal(false); + + const restored = wasm.IdentityPublicKeyInCreation.fromJSON(json); + expect(restored.keyId).to.equal(publicKeyInCreation.keyId); + expect(restored.purpose).to.equal(publicKeyInCreation.purpose); + expect(restored.securityLevel).to.equal(publicKeyInCreation.securityLevel); + expect(restored.keyType).to.equal(publicKeyInCreation.keyType); + expect(restored.readOnly).to.equal(publicKeyInCreation.readOnly); + }); + + it('should export toObject', () => { + const publicKeyInCreation = new wasm.IdentityPublicKeyInCreation( + 0, + 'AUTHENTICATION', + 'master', + 'ECDSA_SECP256K1', + false, + Buffer.from('0333d5cf3674001d2f64c55617b7b11a2e8fc62aab09708b49355e30c7205bdb2e', 'hex'), + [], + ); + + const obj = publicKeyInCreation.toObject(); + // toObject exports with byte arrays which don't round-trip in serde_wasm_bindgen + // but it should at least be defined + expect(obj).to.not.be.undefined; + expect(obj).to.be.an('object'); + }); + }); }); diff --git a/packages/wasm-dpp2/tests/unit/ResourceVoteChoice.spec.mjs b/packages/wasm-dpp2/tests/unit/ResourceVoteChoice.spec.mjs new file mode 100644 index 00000000000..a545a1cef01 --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/ResourceVoteChoice.spec.mjs @@ -0,0 +1,114 @@ +import getWasm from './helpers/wasm.js'; + +let wasm; + +before(async () => { + wasm = await getWasm(); +}); + +describe('ResourceVoteChoice', () => { + const identityIdHex = '1111111111111111111111111111111111111111111111111111111111111111'; + + describe('static constructors', () => { + it('should create TowardsIdentity choice', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const choice = wasm.ResourceVoteChoice.TowardsIdentity(identityId); + + expect(choice.getType()).to.equal('TowardsIdentity'); + expect(choice.getValue()).to.not.be.undefined; + }); + + it('should create Abstain choice', () => { + const choice = wasm.ResourceVoteChoice.Abstain(); + + expect(choice.getType()).to.equal('Abstain'); + expect(choice.getValue()).to.be.undefined; + }); + + it('should create Lock choice', () => { + const choice = wasm.ResourceVoteChoice.Lock(); + + expect(choice.getType()).to.equal('Lock'); + expect(choice.getValue()).to.be.undefined; + }); + }); + + describe('type properties', () => { + it('should return correct __type', () => { + const choice = wasm.ResourceVoteChoice.Abstain(); + + expect(choice.__type).to.equal('ResourceVoteChoice'); + }); + }); + + describe('conversion methods', () => { + it('should round-trip TowardsIdentity via toJSON/fromJSON', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const choice = wasm.ResourceVoteChoice.TowardsIdentity(identityId); + + const json = choice.toJSON(); + expect(json).to.be.an('object'); + + const restored = wasm.ResourceVoteChoice.fromJSON(json); + expect(restored.getType()).to.equal(choice.getType()); + }); + + it('should round-trip Abstain via toJSON/fromJSON', () => { + const choice = wasm.ResourceVoteChoice.Abstain(); + + const json = choice.toJSON(); + // Simple enum variants serialize to strings in serde + expect(json).to.equal('abstain'); + + const restored = wasm.ResourceVoteChoice.fromJSON(json); + expect(restored.getType()).to.equal('Abstain'); + }); + + it('should round-trip Lock via toJSON/fromJSON', () => { + const choice = wasm.ResourceVoteChoice.Lock(); + + const json = choice.toJSON(); + // Simple enum variants serialize to strings in serde + expect(json).to.equal('lock'); + + const restored = wasm.ResourceVoteChoice.fromJSON(json); + expect(restored.getType()).to.equal('Lock'); + }); + + it('should round-trip TowardsIdentity via toObject/fromObject', () => { + const identityId = wasm.Identifier.fromBytes(Buffer.from(identityIdHex, 'hex')); + const choice = wasm.ResourceVoteChoice.TowardsIdentity(identityId); + + const obj = choice.toObject(); + expect(obj).to.be.an('object'); + // Serde serializes enum variants as { variantName: value } with camelCase + expect(obj.towardsIdentity).to.not.be.undefined; + + const restored = wasm.ResourceVoteChoice.fromObject(obj); + expect(restored.getType()).to.equal(choice.getType()); + expect(restored.getValue().toBase58()).to.equal(identityId.toBase58()); + }); + + it('should round-trip Abstain via toObject/fromObject', () => { + const choice = wasm.ResourceVoteChoice.Abstain(); + + const obj = choice.toObject(); + // Simple enum variants serialize to strings in serde + expect(obj).to.equal('abstain'); + + const restored = wasm.ResourceVoteChoice.fromObject(obj); + expect(restored.getType()).to.equal('Abstain'); + }); + + it('should round-trip Lock via toObject/fromObject', () => { + const choice = wasm.ResourceVoteChoice.Lock(); + + const obj = choice.toObject(); + // Simple enum variants serialize to strings in serde + expect(obj).to.equal('lock'); + + const restored = wasm.ResourceVoteChoice.fromObject(obj); + expect(restored.getType()).to.equal('Lock'); + }); + }); +}); diff --git a/packages/wasm-dpp2/tests/unit/TokenBaseTransition.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenBaseTransition.spec.mjs index bbc9bee7c22..5728ab4030c 100644 --- a/packages/wasm-dpp2/tests/unit/TokenBaseTransition.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenBaseTransition.spec.mjs @@ -32,13 +32,13 @@ describe('TokenBaseTransition', function () { it('should allow to get dataContractId', () => { const baseTransition = new wasm.TokenBaseTransition(BigInt(1), 1, dataContractId, ownerId) - expect(baseTransition.dataContractId.base58()).to.deep.equal(dataContractId) + expect(baseTransition.dataContractId.toBase58()).to.deep.equal(dataContractId) }) it('should allow to get tokenId', () => { const baseTransition = new wasm.TokenBaseTransition(BigInt(1), 1, dataContractId, ownerId) - expect(baseTransition.tokenId.base58()).to.deep.equal(ownerId) + expect(baseTransition.tokenId.toBase58()).to.deep.equal(ownerId) }) it('should allow to get usingGroupInfo', () => { @@ -73,7 +73,7 @@ describe('TokenBaseTransition', function () { baseTransition.dataContractId = ownerId - expect(baseTransition.dataContractId.base58()).to.deep.equal(ownerId) + expect(baseTransition.dataContractId.toBase58()).to.deep.equal(ownerId) }) it('should allow to set tokenId', () => { @@ -81,7 +81,7 @@ describe('TokenBaseTransition', function () { baseTransition.tokenId = dataContractId - expect(baseTransition.tokenId.base58()).to.deep.equal(dataContractId) + expect(baseTransition.tokenId.toBase58()).to.deep.equal(dataContractId) }) it('should allow to set usingGroupInfo', () => { diff --git a/packages/wasm-dpp2/tests/unit/TokenConfiguration.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenConfiguration.spec.mjs index 0375a69ec1c..5ece4fccd22 100644 --- a/packages/wasm-dpp2/tests/unit/TokenConfiguration.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenConfiguration.spec.mjs @@ -12,6 +12,7 @@ describe('TokenConfiguration', () => { const convention = new wasm.TokenConfigurationConvention( { ru: { + $format_version: '0', shouldCapitalize: true, singularForm: 'TOKEN', pluralForm: 'TOKENS', @@ -96,6 +97,7 @@ describe('TokenConfiguration', () => { const convention = new wasm.TokenConfigurationConvention( { ru: { + $format_version: '0', shouldCapitalize: true, singularForm: 'TOKEN', pluralForm: 'TOKENS', diff --git a/packages/wasm-dpp2/tests/unit/TokenConfigurationConvention.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenConfigurationConvention.spec.mjs index a639308ef0a..60766ba9925 100644 --- a/packages/wasm-dpp2/tests/unit/TokenConfigurationConvention.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenConfigurationConvention.spec.mjs @@ -93,6 +93,7 @@ describe('TokenConfigurationConvention', () => { expect(convention.localizations.ru).to.deep.equal(undefined); expect(convention.localizations.en.constructor.name).to.deep.equal('TokenConfigurationLocalization'); expect(convention.localizations.en.toJSON()).to.deep.equal({ + $format_version: '0', shouldCapitalize: false, singularForm: 'singularForm', pluralForm: 'pluralForm', diff --git a/packages/wasm-dpp2/tests/unit/TokenConfigurationLocalization.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenConfigurationLocalization.spec.mjs index 55585958baa..6ae96277d11 100644 --- a/packages/wasm-dpp2/tests/unit/TokenConfigurationLocalization.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenConfigurationLocalization.spec.mjs @@ -13,6 +13,24 @@ describe('TokenConfigurationLocalization', () => { expect(localization.__wbg_ptr).to.not.equal(0); }); + + it('should recreate localization from JSON', () => { + const localization = new wasm.TokenConfigurationLocalization(false, 'singularForm', 'pluralForm'); + const json = localization.toJSON(); + + const restored = wasm.TokenConfigurationLocalization.fromJSON(json); + + expect(restored.toJSON()).to.deep.equal(json); + }); + + it('should recreate localization from object', () => { + const localization = new wasm.TokenConfigurationLocalization(false, 'singularForm', 'pluralForm'); + const object = localization.toJSON(); + + const restored = wasm.TokenConfigurationLocalization.fromObject(object); + + expect(restored.toJSON()).to.deep.equal(object); + }); }); describe('getters', () => { diff --git a/packages/wasm-dpp2/tests/unit/TokenDistributionRecipient.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenDistributionRecipient.spec.mjs index 90a14ccb3aa..5674cbacc87 100644 --- a/packages/wasm-dpp2/tests/unit/TokenDistributionRecipient.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenDistributionRecipient.spec.mjs @@ -40,7 +40,7 @@ describe('TokenDistributionRecipient', () => { const recipient = wasm.TokenDistributionRecipient.Identity(identifier); expect(recipient.getType()).to.equal(`Identity(${identifier})`); - expect(recipient.getValue().base58()).to.equal(identifier); + expect(recipient.getValue().toBase58()).to.equal(identifier); }); it('should allow to get values EvonodesByParticipation', () => { diff --git a/packages/wasm-dpp2/tests/unit/TokenDistributionRules.spec.mjs b/packages/wasm-dpp2/tests/unit/TokenDistributionRules.spec.mjs index 6dee286c9ff..af70551feae 100644 --- a/packages/wasm-dpp2/tests/unit/TokenDistributionRules.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokenDistributionRules.spec.mjs @@ -263,7 +263,7 @@ describe('TokenDistributionRules', () => { it('should allow to set newTokenDestinationIdentity', () => { distributionRules.newTokenDestinationIdentity = '12p3355tKpjLinncBYeMsXkdDYXCbsFzzVmssce6pSJ1'; - expect(distributionRules.newTokenDestinationIdentity.base58()).to.deep.equal('12p3355tKpjLinncBYeMsXkdDYXCbsFzzVmssce6pSJ1'); + expect(distributionRules.newTokenDestinationIdentity.toBase58()).to.deep.equal('12p3355tKpjLinncBYeMsXkdDYXCbsFzzVmssce6pSJ1'); }); it('should allow to set preProgrammedDistribution', () => { diff --git a/packages/wasm-dpp2/tests/unit/TokensTransitions.spec.mjs b/packages/wasm-dpp2/tests/unit/TokensTransitions.spec.mjs index 04ae362bdc6..8f90d900e20 100644 --- a/packages/wasm-dpp2/tests/unit/TokensTransitions.spec.mjs +++ b/packages/wasm-dpp2/tests/unit/TokensTransitions.spec.mjs @@ -240,7 +240,7 @@ describe('TokenTransitions', () => { ); expect(freezeTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(freezeTransition.frozenIdentityId.base58()).to.equal(ownerId); + expect(freezeTransition.frozenIdentityId.toBase58()).to.equal(ownerId); expect(freezeTransition.publicNote).to.equal('bbbb'); }); @@ -252,7 +252,7 @@ describe('TokenTransitions', () => { ); expect(unfreezeTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(unfreezeTransition.frozenIdentityId.base58()).to.equal(ownerId); + expect(unfreezeTransition.frozenIdentityId.toBase58()).to.equal(ownerId); expect(unfreezeTransition.publicNote).to.equal('bbbb'); }); @@ -264,7 +264,7 @@ describe('TokenTransitions', () => { ); expect(tokenDestroyFrozenFundsTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(tokenDestroyFrozenFundsTransition.frozenIdentityId.base58()).to.equal(ownerId); + expect(tokenDestroyFrozenFundsTransition.frozenIdentityId.toBase58()).to.equal(ownerId); expect(tokenDestroyFrozenFundsTransition.publicNote).to.equal('bbbb'); }); @@ -397,7 +397,7 @@ describe('TokenTransitions', () => { freezeTransition.publicNote = 'aaaa'; expect(freezeTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(freezeTransition.frozenIdentityId.base58()).to.equal(dataContractId); + expect(freezeTransition.frozenIdentityId.toBase58()).to.equal(dataContractId); expect(freezeTransition.publicNote).to.equal('aaaa'); }); @@ -412,7 +412,7 @@ describe('TokenTransitions', () => { unfreezeTransition.publicNote = 'aaaa'; expect(unfreezeTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(unfreezeTransition.frozenIdentityId.base58()).to.equal(dataContractId); + expect(unfreezeTransition.frozenIdentityId.toBase58()).to.equal(dataContractId); expect(unfreezeTransition.publicNote).to.equal('aaaa'); }); @@ -427,7 +427,7 @@ describe('TokenTransitions', () => { tokenDestroyFrozenFundsTransition.publicNote = 'aaaa'; expect(tokenDestroyFrozenFundsTransition.base.constructor.name).to.equal('TokenBaseTransition'); - expect(tokenDestroyFrozenFundsTransition.frozenIdentityId.base58()).to.equal(dataContractId); + expect(tokenDestroyFrozenFundsTransition.frozenIdentityId.toBase58()).to.equal(dataContractId); expect(tokenDestroyFrozenFundsTransition.publicNote).to.equal('aaaa'); }); diff --git a/packages/wasm-dpp2/tests/unit/TxOut.spec.mjs b/packages/wasm-dpp2/tests/unit/TxOut.spec.mjs deleted file mode 100644 index 86971b1390f..00000000000 --- a/packages/wasm-dpp2/tests/unit/TxOut.spec.mjs +++ /dev/null @@ -1,74 +0,0 @@ -import getWasm from './helpers/wasm.js'; - -let wasm; - -before(async () => { - wasm = await getWasm(); -}); - -describe('TxOut', () => { - describe('serialization / deserialization', () => { - it('should allow to create from values with pubkey hex', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - expect(out.__wbg_ptr).to.not.equal(0); - }); - - it('should allow to create from values with pubkey array', () => { - const out = new wasm.TxOut(BigInt(100), Array.from(Buffer.from('76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac', 'hex'))); - - expect(out.__wbg_ptr).to.not.equal(0); - }); - }); - - describe('getters', () => { - it('should allow to get value', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - expect(out.value).to.equal(BigInt(100)); - }); - - it('should allow to get script hex', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - expect(out.scriptPubKeyHex).to.equal('76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - }); - - it('should allow to get script bytes', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - expect(out.scriptPubKeyBytes).to.deep.equal(Uint8Array.from(Buffer.from('76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac', 'hex'))); - }); - - it('should allow to get script asm', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - expect(out.getScriptPubKeyASM()).to.deep.equal('OP_DUP OP_HASH160 OP_PUSHBYTES_20 1a486a3855e6dc6dd02874424f53a6f2197b3d45 OP_EQUALVERIFY OP_CHECKSIG'); - }); - }); - - describe('setters', () => { - it('should allow to set value', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - out.value = BigInt(101); - - expect(out.value).to.equal(BigInt(101)); - }); - - it('should allow to set script hex', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - out.scriptPubKeyHex = '16a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'; - - expect(out.scriptPubKeyHex).to.equal('16a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - }); - - it('should allow to set script bytes', () => { - const out = new wasm.TxOut(BigInt(100), '76a9141a486a3855e6dc6dd02874424f53a6f2197b3d4588ac'); - - out.scriptPubKeyBytes = Array.from(Buffer.from('76a914f995e42d1aa7a31b0106b63e1b896fe9aeeccc9988ac', 'hex')); - - expect(out.scriptPubKeyBytes).to.deep.equal(Uint8Array.from(Buffer.from('76a914f995e42d1aa7a31b0106b63e1b896fe9aeeccc9988ac', 'hex'))); - }); - }); -}); diff --git a/packages/wasm-dpp2/tests/unit/mocks/DataContract/index.js b/packages/wasm-dpp2/tests/unit/mocks/DataContract/index.js index bdb1e14f68b..7526ee8cc5e 100644 --- a/packages/wasm-dpp2/tests/unit/mocks/DataContract/index.js +++ b/packages/wasm-dpp2/tests/unit/mocks/DataContract/index.js @@ -1,6 +1,8 @@ -import value from './value.js'; +import json from './json.js'; +import object from './object.js'; -export { value }; +// Keep 'value' as alias to json for backward compatibility with existing tests +export { json as value, json, object }; export const id = '4fJLR2GYTPFdomuTVvNy3VRrvWgvkKPzqehEBpNf2nk6' export const ownerId = '11111111111111111111111111111111' export const dataContractsBytes = ['00ea8920edea52fa400de2ccc43052b2821f45585241a2875d4250e35d4ce937c800000000000101000001ab321b19457b1c16b12762509276d47d952f582d978cbd8b50bbfc2fd8a7767d0001046e6f7465160312047479706512066f626a656374120a70726f70657274696573160112076d65737361676516021204747970651206737472696e671208706f736974696f6e030012146164646974696f6e616c50726f706572746965731300'] diff --git a/packages/wasm-dpp2/tests/unit/mocks/DataContract/value.js b/packages/wasm-dpp2/tests/unit/mocks/DataContract/json.js similarity index 100% rename from packages/wasm-dpp2/tests/unit/mocks/DataContract/value.js rename to packages/wasm-dpp2/tests/unit/mocks/DataContract/json.js diff --git a/packages/wasm-dpp2/tests/unit/mocks/DataContract/object.js b/packages/wasm-dpp2/tests/unit/mocks/DataContract/object.js new file mode 100644 index 00000000000..372869e47a2 --- /dev/null +++ b/packages/wasm-dpp2/tests/unit/mocks/DataContract/object.js @@ -0,0 +1,157 @@ +/* eslint-disable quote-props, quotes */ +import bs58 from 'bs58'; + +// Object format uses Uint8Array for identifiers and BigInt for integers +export default { + "$format_version": "0", + "id": bs58.decode("4fJLR2GYTPFdomuTVvNy3VRrvWgvkKPzqehEBpNf2nk6"), + "config": { + "$format_version": "0", + "canBeDeleted": false, + "readonly": false, + "keepsHistory": false, + "documentsKeepHistoryContractDefault": false, + "documentsMutableContractDefault": true, + "documentsCanBeDeletedContractDefault": true, + "requiresIdentityEncryptionBoundedKey": undefined, + "requiresIdentityDecryptionBoundedKey": undefined + }, + "version": 1n, + "ownerId": bs58.decode("11111111111111111111111111111111"), + "schemaDefs": null, + "documentSchemas": { + "withdrawal": { + "description": "Withdrawal document to track underlying withdrawal transactions. Withdrawals should be created with IdentityWithdrawalTransition", + "creationRestrictionMode": 2n, + "type": "object", + "indices": [ + { + "name": "identityStatus", + "properties": [ + { + "$ownerId": "asc" + }, + { + "status": "asc" + }, + { + "$createdAt": "asc" + } + ], + "unique": false + }, + { + "name": "identityRecent", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + }, + { + "status": "asc" + } + ], + "unique": false + }, + { + "name": "pooling", + "properties": [ + { + "status": "asc" + }, + { + "pooling": "asc" + }, + { + "coreFeePerByte": "asc" + }, + { + "$updatedAt": "asc" + } + ], + "unique": false + }, + { + "name": "transaction", + "properties": [ + { + "status": "asc" + }, + { + "transactionIndex": "asc" + } + ], + "unique": false + } + ], + "properties": { + "transactionIndex": { + "type": "integer", + "description": "Sequential index of asset unlock (withdrawal) transaction. Populated when a withdrawal pooled into withdrawal transaction", + "minimum": 1n, + "position": 0n + }, + "transactionSignHeight": { + "type": "integer", + "description": "The Core height on which transaction was signed", + "minimum": 1n, + "position": 1n + }, + "amount": { + "type": "integer", + "description": "The amount to be withdrawn", + "minimum": 1000n, + "position": 2n + }, + "coreFeePerByte": { + "type": "integer", + "description": "This is the fee that you are willing to spend for this transaction in Duffs/Byte", + "minimum": 1n, + "maximum": 4294967295n, + "position": 3n + }, + "pooling": { + "type": "integer", + "description": "This indicated the level at which Platform should try to pool this transaction", + "enum": [ + 0n, + 1n, + 2n + ], + "position": 4n + }, + "outputScript": { + "type": "array", + "byteArray": true, + "minItems": 23n, + "maxItems": 25n, + "position": 5n + }, + "status": { + "type": "integer", + "enum": [ + 0n, + 1n, + 2n, + 3n, + 4n + ], + "description": "0 - Pending, 1 - Signed, 2 - Broadcasted, 3 - Complete, 4 - Expired", + "position": 6n + } + }, + "additionalProperties": false, + "required": [ + "$createdAt", + "$updatedAt", + "amount", + "coreFeePerByte", + "pooling", + "outputScript", + "status" + ] + } + } +} diff --git a/packages/wasm-dpp2/tests/unit/mocks/Locks/index.js b/packages/wasm-dpp2/tests/unit/mocks/Locks/index.js index c13706e2a7f..d5db4e24511 100644 --- a/packages/wasm-dpp2/tests/unit/mocks/Locks/index.js +++ b/packages/wasm-dpp2/tests/unit/mocks/Locks/index.js @@ -1,2 +1,2 @@ -export const instantLockBytes = [1, 1, 89, 68, 206, 254, 139, 111, 183, 115, 169, 122, 19, 26, 29, 175, 97, 14, 156, 207, 215, 16, 92, 152, 110, 29, 167, 34, 91, 149, 243, 48, 250, 217, 0, 0, 0, 0, 61, 111, 50, 237, 225, 255, 25, 159, 133, 89, 244, 234, 150, 19, 61, 198, 218, 144, 144, 44, 45, 189, 109, 187, 247, 117, 93, 95, 156, 46, 12, 216, 1, 49, 33, 55, 67, 236, 100, 131, 20, 54, 98, 71, 159, 60, 21, 2, 116, 197, 18, 154, 171, 227, 15, 30, 21, 0, 0, 0, 0, 0, 0, 0, 169, 241, 49, 98, 108, 73, 162, 241, 131, 183, 162, 245, 99, 173, 29, 197, 10, 200, 34, 1, 144, 219, 237, 184, 5, 32, 155, 96, 142, 184, 100, 224, 29, 98, 241, 139, 201, 250, 166, 10, 139, 138, 39, 245, 160, 199, 200, 185, 20, 250, 58, 20, 54, 10, 47, 37, 85, 142, 224, 224, 166, 147, 177, 143, 172, 203, 181, 158, 195, 155, 155, 60, 174, 67, 14, 11, 118, 235, 8, 7, 82, 206, 16, 61, 247, 101, 55, 161, 165, 131, 104, 10, 89, 20, 82, 157]; -export const transactionBytes = [3, 0, 8, 0, 1, 89, 68, 206, 254, 139, 111, 183, 115, 169, 122, 19, 26, 29, 175, 97, 14, 156, 207, 215, 16, 92, 152, 110, 29, 167, 34, 91, 149, 243, 48, 250, 217, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 157, 215, 234, 11, 75, 36, 23, 153, 82, 143, 163, 76, 75, 118, 142, 19, 6, 13, 64, 50, 24, 199, 141, 199, 163, 14, 86, 81, 67, 150, 240, 28, 2, 32, 105, 238, 106, 123, 228, 237, 166, 210, 251, 103, 249, 36, 15, 233, 177, 30, 40, 125, 67, 108, 224, 183, 4, 5, 18, 239, 231, 185, 223, 195, 92, 240, 1, 33, 3, 238, 76, 67, 141, 205, 25, 38, 200, 164, 105, 53, 55, 189, 36, 52, 232, 121, 166, 108, 186, 227, 161, 55, 7, 105, 135, 102, 219, 224, 117, 213, 70, 255, 255, 255, 255, 2, 128, 29, 44, 4, 0, 0, 0, 0, 2, 106, 0, 48, 232, 38, 1, 0, 0, 0, 0, 25, 118, 169, 20, 232, 101, 217, 39, 144, 120, 74, 135, 230, 203, 134, 239, 207, 15, 238, 152, 16, 172, 45, 177, 136, 172, 0, 0, 0, 0, 36, 1, 1, 128, 29, 44, 4, 0, 0, 0, 0, 25, 118, 169, 20, 12, 182, 70, 130, 142, 161, 106, 83, 94, 200, 33, 4, 251, 5, 50, 93, 28, 107, 159, 168, 136, 172]; +export const instantLockBytes = new Uint8Array([1, 1, 89, 68, 206, 254, 139, 111, 183, 115, 169, 122, 19, 26, 29, 175, 97, 14, 156, 207, 215, 16, 92, 152, 110, 29, 167, 34, 91, 149, 243, 48, 250, 217, 0, 0, 0, 0, 61, 111, 50, 237, 225, 255, 25, 159, 133, 89, 244, 234, 150, 19, 61, 198, 218, 144, 144, 44, 45, 189, 109, 187, 247, 117, 93, 95, 156, 46, 12, 216, 1, 49, 33, 55, 67, 236, 100, 131, 20, 54, 98, 71, 159, 60, 21, 2, 116, 197, 18, 154, 171, 227, 15, 30, 21, 0, 0, 0, 0, 0, 0, 0, 169, 241, 49, 98, 108, 73, 162, 241, 131, 183, 162, 245, 99, 173, 29, 197, 10, 200, 34, 1, 144, 219, 237, 184, 5, 32, 155, 96, 142, 184, 100, 224, 29, 98, 241, 139, 201, 250, 166, 10, 139, 138, 39, 245, 160, 199, 200, 185, 20, 250, 58, 20, 54, 10, 47, 37, 85, 142, 224, 224, 166, 147, 177, 143, 172, 203, 181, 158, 195, 155, 155, 60, 174, 67, 14, 11, 118, 235, 8, 7, 82, 206, 16, 61, 247, 101, 55, 161, 165, 131, 104, 10, 89, 20, 82, 157]); +export const transactionBytes = new Uint8Array([3, 0, 8, 0, 1, 89, 68, 206, 254, 139, 111, 183, 115, 169, 122, 19, 26, 29, 175, 97, 14, 156, 207, 215, 16, 92, 152, 110, 29, 167, 34, 91, 149, 243, 48, 250, 217, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 157, 215, 234, 11, 75, 36, 23, 153, 82, 143, 163, 76, 75, 118, 142, 19, 6, 13, 64, 50, 24, 199, 141, 199, 163, 14, 86, 81, 67, 150, 240, 28, 2, 32, 105, 238, 106, 123, 228, 237, 166, 210, 251, 103, 249, 36, 15, 233, 177, 30, 40, 125, 67, 108, 224, 183, 4, 5, 18, 239, 231, 185, 223, 195, 92, 240, 1, 33, 3, 238, 76, 67, 141, 205, 25, 38, 200, 164, 105, 53, 55, 189, 36, 52, 232, 121, 166, 108, 186, 227, 161, 55, 7, 105, 135, 102, 219, 224, 117, 213, 70, 255, 255, 255, 255, 2, 128, 29, 44, 4, 0, 0, 0, 0, 2, 106, 0, 48, 232, 38, 1, 0, 0, 0, 0, 25, 118, 169, 20, 232, 101, 217, 39, 144, 120, 74, 135, 230, 203, 134, 239, 207, 15, 238, 152, 16, 172, 45, 177, 136, 172, 0, 0, 0, 0, 36, 1, 1, 128, 29, 44, 4, 0, 0, 0, 0, 25, 118, 169, 20, 12, 182, 70, 130, 142, 161, 106, 83, 94, 200, 33, 4, 251, 5, 50, 93, 28, 107, 159, 168, 136, 172]); diff --git a/packages/wasm-dpp2/tests/unit/mocks/TokenConfiguration/index.js b/packages/wasm-dpp2/tests/unit/mocks/TokenConfiguration/index.js index 8a46860c764..1f4a8fe26fc 100644 --- a/packages/wasm-dpp2/tests/unit/mocks/TokenConfiguration/index.js +++ b/packages/wasm-dpp2/tests/unit/mocks/TokenConfiguration/index.js @@ -1,4 +1,5 @@ export const tokenLocalization = { + $format_version: '0', shouldCapitalize: true, singularForm: 'TOKEN', pluralForm: 'TOKENS', diff --git a/packages/wasm-sdk/Cargo.toml b/packages/wasm-sdk/Cargo.toml index 0f92fb8856a..7cea0aa2e42 100644 --- a/packages/wasm-sdk/Cargo.toml +++ b/packages/wasm-sdk/Cargo.toml @@ -76,7 +76,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ tracing-wasm = { version = "0.2.1" } platform-value = { path = "../rs-platform-value", features = ["json"] } serde = { version = "1.0", features = ["derive"] } -serde-wasm-bindgen = { version = "0.6.5" } +serde-wasm-bindgen = { git = "https://github.com/dashpay/serde-wasm-bindgen", branch = "fix/uint8array-to-bytes" } serde_json = "1.0" hex = "0.4" base64 = "0.22" diff --git a/packages/wasm-sdk/src/dpns.rs b/packages/wasm-sdk/src/dpns.rs index 63ac7086701..515dc732c37 100644 --- a/packages/wasm-sdk/src/dpns.rs +++ b/packages/wasm-sdk/src/dpns.rs @@ -1,6 +1,7 @@ use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::{deserialize_required_query, identifier_from_js}; -use crate::queries::{ProofInfoWasm, ProofMetadataResponseWasm, ResponseMetadataWasm}; +use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use dash_sdk::dpp::document::{Document, DocumentV0Getters}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; @@ -20,28 +21,44 @@ use wasm_bindgen::prelude::*; use wasm_dpp2::identifier::IdentifierWasm; #[wasm_bindgen(js_name = "RegisterDpnsNameResult")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RegisterDpnsNameResult { #[wasm_bindgen(getter_with_clone, js_name = "preorderDocumentId")] - pub preorder_document_id: String, + pub preorder_document_id: IdentifierWasm, #[wasm_bindgen(getter_with_clone, js_name = "domainDocumentId")] - pub domain_document_id: String, + pub domain_document_id: IdentifierWasm, #[wasm_bindgen(getter_with_clone, js_name = "fullDomainName")] pub full_domain_name: String, } #[wasm_bindgen(js_name = "DpnsUsernameInfo")] -#[derive(Clone, Serialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DpnsUsernameInfo { #[wasm_bindgen(getter_with_clone)] pub username: String, #[wasm_bindgen(getter_with_clone, js_name = "identityId")] - pub identity_id: String, + pub identity_id: IdentifierWasm, #[wasm_bindgen(getter_with_clone, js_name = "documentId")] - pub document_id: String, + pub document_id: IdentifierWasm, } +#[wasm_bindgen(js_class = DpnsUsernameInfo)] +impl DpnsUsernameInfo { + #[wasm_bindgen(constructor)] + pub fn new(username: String, identity_id: IdentifierWasm, document_id: IdentifierWasm) -> Self { + Self { + username, + identity_id, + document_id, + } + } +} + +impl_wasm_serde_conversions!(RegisterDpnsNameResult); +impl_wasm_serde_conversions!(DpnsUsernameInfo); + const DEFAULT_DPNS_USERNAMES_LIMIT: u32 = 10; fn resolve_dpns_usernames_limit(limit: Option) -> u32 { @@ -119,27 +136,6 @@ fn parse_dpns_usernames_query( }) } -#[wasm_bindgen(js_name = "DpnsUsernamesProofResponse")] -#[derive(Clone)] -pub struct DpnsUsernamesProofResponseWasm { - #[wasm_bindgen(getter_with_clone)] - pub usernames: Array, - #[wasm_bindgen(getter_with_clone)] - pub metadata: ResponseMetadataWasm, - #[wasm_bindgen(getter_with_clone)] - pub proof: ProofInfoWasm, -} - -#[wasm_bindgen(js_name = "DpnsUsernameProofResponse")] -#[derive(Clone)] -pub struct DpnsUsernameProofResponseWasm { - #[wasm_bindgen(getter_with_clone)] - pub username: JsValue, - #[wasm_bindgen(getter_with_clone)] - pub metadata: ResponseMetadataWasm, - #[wasm_bindgen(getter_with_clone)] - pub proof: ProofInfoWasm, -} impl WasmSdk { async fn prepare_dpns_usernames_query( &self, @@ -189,7 +185,7 @@ impl WasmSdk { &self, identity_id: Identifier, limit: Option, - ) -> Result { + ) -> Result { let query = self .prepare_dpns_usernames_query(identity_id, limit) .await?; @@ -197,11 +193,11 @@ impl WasmSdk { Document::fetch_many_with_metadata_and_proof(self.as_ref(), query, None).await?; let usernames_array = usernames_from_documents(documents_result); - Ok(DpnsUsernamesProofResponseWasm { - usernames: usernames_array, - metadata: metadata.into(), - proof: proof.into(), - }) + Ok(ProofMetadataResponseWasm::from_sdk_parts( + usernames_array, + metadata, + proof, + )) } } @@ -263,6 +259,7 @@ impl WasmSdk { let callback_box = if preorder_callback.is_some() { Some(Box::new(move |doc: &Document| { + // TODO: Pass document instead of JSON PREORDER_CALLBACK.with(|cb| { if let Some(js_callback) = cb.borrow().as_ref() { let preorder_info = serde_json::json!( @@ -274,7 +271,9 @@ impl WasmSdk { .created_at_core_block_height(), "message" : "Preorder document submitted successfully", } ); - if let Ok(js_value) = serde_wasm_bindgen::to_value(&preorder_info) { + // Use json_compatible() to ensure objects become plain JS objects (not Maps) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + if let Ok(js_value) = preorder_info.serialize(&serializer) { let _ = js_callback.call1(&JsValue::NULL, &js_value); } } @@ -299,8 +298,8 @@ impl WasmSdk { }); Ok(RegisterDpnsNameResult { - preorder_document_id: result.preorder_document.id().to_string(Encoding::Base58), - domain_document_id: result.domain_document.id().to_string(Encoding::Base58), + preorder_document_id: IdentifierWasm::from(result.preorder_document.id()), + domain_document_id: IdentifierWasm::from(result.domain_document.id()), full_domain_name: result.full_domain_name, }) } @@ -366,8 +365,8 @@ impl WasmSdk { if let Some((_, Some(document))) = documents.into_iter().next() { Ok(DpnsUsernameInfo { username: username.to_string(), - identity_id: document.owner_id().to_string(Encoding::Base58), - document_id: document.id().to_string(Encoding::Base58), + identity_id: IdentifierWasm::from(document.owner_id()), + document_id: IdentifierWasm::from(document.id()), }) } else { Err(WasmSdkError::not_found(format!( @@ -427,16 +426,17 @@ impl WasmSdk { if let Some((_, Some(document))) = documents.into_iter().next() { let result = DpnsUsernameInfo { username: username.to_string(), - identity_id: document.owner_id().to_string(Encoding::Base58), - document_id: document.id().to_string(Encoding::Base58), + identity_id: IdentifierWasm::from(document.owner_id()), + document_id: IdentifierWasm::from(document.id()), }; - let data = serde_wasm_bindgen::to_value(&result).map_err(|e| { + // Use json_compatible() to ensure objects become plain JS objects (not Maps) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + let data = result.serialize(&serializer).map_err(|e| { WasmSdkError::serialization(format!("Failed to serialize username info: {}", e)) })?; - let response = - ProofMetadataResponseWasm::from_parts(data, metadata.into(), proof.into()); + let response = ProofMetadataResponseWasm::from_sdk_parts(data, metadata, proof); Ok(response) } else { @@ -480,43 +480,44 @@ impl WasmSdk { .map(Some) .ok_or_else(|| WasmSdkError::generic("DPNS username is not a string")) } - #[wasm_bindgen(js_name = "getDpnsUsernamesWithProofInfo")] + #[wasm_bindgen( + js_name = "getDpnsUsernamesWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] pub async fn get_dpns_usernames_with_proof_info( &self, query: DpnsUsernamesQueryJs, - ) -> Result { + ) -> Result { let params = parse_dpns_usernames_query(query)?; self.fetch_dpns_usernames_with_proof(params.identity_id, params.limit) .await } - #[wasm_bindgen(js_name = "getDpnsUsernameWithProofInfo")] + #[wasm_bindgen( + js_name = "getDpnsUsernameWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped" + )] pub async fn get_dpns_username_with_proof_info( &self, #[wasm_bindgen(js_name = "identityId")] #[wasm_bindgen(unchecked_param_type = "IdentifierLike")] identity_id: JsValue, - ) -> Result { + ) -> Result { let identity_id_parsed = identifier_from_js(&identity_id, "identity ID")?; - let DpnsUsernamesProofResponseWasm { - usernames, - metadata, - proof, - } = self + let mut response = self .fetch_dpns_usernames_with_proof(identity_id_parsed, Some(1)) .await?; + let usernames = js_sys::Array::from(&response.data()); let username = if usernames.length() > 0 { usernames.get(0) } else { JsValue::NULL }; - Ok(DpnsUsernameProofResponseWasm { - username, - metadata, - proof, - }) + response.set_data(username); + + Ok(response) } } diff --git a/packages/wasm-sdk/src/error.rs b/packages/wasm-sdk/src/error.rs index acf717619da..2f4680f4627 100644 --- a/packages/wasm-sdk/src/error.rs +++ b/packages/wasm-sdk/src/error.rs @@ -2,6 +2,7 @@ use dash_sdk::dpp::ProtocolError; use dash_sdk::{error::StateTransitionBroadcastError, Error as SdkError}; use rs_dapi_client::CanRetry; use wasm_bindgen::prelude::wasm_bindgen; +use wasm_dpp2::error::WasmDppError; /// Structured error surfaced to JS consumers #[wasm_bindgen] @@ -196,6 +197,18 @@ impl From for WasmSdkError { } } +impl From for WasmSdkError { + fn from(err: WasmDppError) -> Self { + // Map WasmDppError to appropriate WasmSdkError kind + Self::new( + WasmSdkErrorKind::SerializationError, + err.to_string(), + None, + false, + ) + } +} + #[wasm_bindgen] impl WasmSdkError { /// Error kind (enum) diff --git a/packages/wasm-sdk/src/lib.rs b/packages/wasm-sdk/src/lib.rs index 790da0f6589..63a66f12e63 100644 --- a/packages/wasm-sdk/src/lib.rs +++ b/packages/wasm-sdk/src/lib.rs @@ -6,6 +6,7 @@ pub mod error; pub mod logging; pub mod queries; pub mod sdk; +pub mod serialization; pub mod state_transitions; pub mod utils; pub mod wallet; diff --git a/packages/wasm-sdk/src/queries/data_contract.rs b/packages/wasm-sdk/src/queries/data_contract.rs index b9b79c1fe85..b14637f686d 100644 --- a/packages/wasm-sdk/src/queries/data_contract.rs +++ b/packages/wasm-sdk/src/queries/data_contract.rs @@ -133,10 +133,10 @@ impl WasmSdk { contract .map(|contract| { - ProofMetadataResponseWasm::from_parts( - JsValue::from(DataContractWasm::from(contract)), - metadata.into(), - proof.into(), + ProofMetadataResponseWasm::from_sdk_parts( + DataContractWasm::from(contract), + metadata, + proof, ) }) .ok_or_else(|| WasmSdkError::not_found("Data contract not found")) @@ -235,10 +235,10 @@ impl WasmSdk { } } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(history_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + history_map, + metadata, + proof, )) } @@ -279,10 +279,10 @@ impl WasmSdk { contracts_map.set(&key, &JsValue::from(value)); } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(contracts_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + contracts_map, + metadata, + proof, )) } } diff --git a/packages/wasm-sdk/src/queries/document.rs b/packages/wasm-sdk/src/queries/document.rs index e8452b9ea29..af4173b87c5 100644 --- a/packages/wasm-sdk/src/queries/document.rs +++ b/packages/wasm-sdk/src/queries/document.rs @@ -340,8 +340,7 @@ impl WasmSdk { match doc_opt { Some(doc) => { - let wasm_doc = - DocumentWasm::from_batch(doc, contract_id, doc_type_name.clone(), None); + let wasm_doc = DocumentWasm::new(doc, contract_id, doc_type_name.clone(), None); documents_map.set(&key, &JsValue::from(wasm_doc)); } None => { @@ -376,8 +375,7 @@ impl WasmSdk { match doc_opt { Some(doc) => { - let wasm_doc = - DocumentWasm::from_batch(doc, contract_id, doc_type_name.clone(), None); + let wasm_doc = DocumentWasm::new(doc, contract_id, doc_type_name.clone(), None); documents_map.set(&key, &JsValue::from(wasm_doc)); } None => { @@ -386,10 +384,10 @@ impl WasmSdk { } } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(documents_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + documents_map, + metadata, + proof, )) } @@ -435,7 +433,7 @@ impl WasmSdk { // Execute query let document = Document::fetch(self.as_ref(), query) .await? - .map(|doc| DocumentWasm::from_batch(doc, contract_id, document_type.to_string(), None)); + .map(|doc| DocumentWasm::new(doc, contract_id, document_type.to_string(), None)); Ok(document) } @@ -487,12 +485,12 @@ impl WasmSdk { Document::fetch_with_metadata_and_proof(self.as_ref(), query, None).await?; let document_js = document_result - .map(|doc| DocumentWasm::from_batch(doc, contract_id, document_type.to_string(), None)); + .map(|doc| DocumentWasm::new(doc, contract_id, document_type.to_string(), None)); - Ok(ProofMetadataResponseWasm::from_parts( + Ok(ProofMetadataResponseWasm::from_sdk_parts( JsValue::from(document_js), - metadata.into(), - proof.into(), + metadata, + proof, )) } } diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 5ab5ca36b58..8fcfd8b4d38 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -439,10 +439,8 @@ impl WasmSdk { epochs_map.set(&key.into(), &JsValue::from(value)); } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(epochs_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + epochs_map, metadata, proof, )) } @@ -456,10 +454,10 @@ impl WasmSdk { let (epoch, metadata, proof) = ExtendedEpochInfo::fetch_current_with_metadata_and_proof(self.as_ref()).await?; - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(ExtendedEpochInfoWasm::from(epoch)), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + ExtendedEpochInfoWasm::from(epoch), + metadata, + proof, )) } @@ -514,10 +512,8 @@ impl WasmSdk { epochs_map.set(&key.into(), &JsValue::from(value)); } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(epochs_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + epochs_map, metadata, proof, )) } diff --git a/packages/wasm-sdk/src/queries/group.rs b/packages/wasm-sdk/src/queries/group.rs index d49163be015..8d1e2b131bf 100644 --- a/packages/wasm-sdk/src/queries/group.rs +++ b/packages/wasm-sdk/src/queries/group.rs @@ -666,7 +666,7 @@ impl WasmSdk { // Check member data contracts if let Some(contracts) = member_data_contracts { for contract_id in contracts { - let contract_id_str = IdentifierWasm::from(contract_id).get_base58(); + let contract_id_str = IdentifierWasm::from(contract_id).to_base58(); // Fetch all groups for this contract let query = GroupInfosQuery { contract_id, @@ -972,7 +972,7 @@ impl WasmSdk { // Check member data contracts if let Some(contracts) = member_data_contracts { for contract_id in contracts { - let contract_id_str = IdentifierWasm::from(contract_id).get_base58(); + let contract_id_str = IdentifierWasm::from(contract_id).to_base58(); // Fetch all groups for this contract with proof let query = GroupInfosQuery { contract_id, diff --git a/packages/wasm-sdk/src/queries/identity.rs b/packages/wasm-sdk/src/queries/identity.rs index dda304cf1e7..0a59d5377b0 100644 --- a/packages/wasm-sdk/src/queries/identity.rs +++ b/packages/wasm-sdk/src/queries/identity.rs @@ -1,4 +1,5 @@ use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::deserialize_required_query; use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; @@ -11,7 +12,7 @@ use dash_sdk::platform::{Fetch, FetchMany, Identifier, Identity, IdentityKeysQue use drive_proof_verifier::types::{IdentityPublicKeys, IndexMap}; use js_sys::{Array, BigInt, Map, Uint8Array}; use rs_dapi_client::IntoInner; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -113,7 +114,8 @@ impl IdentityContractKeysWasm { } #[wasm_bindgen(js_name = "IdentityBalanceInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct IdentityBalanceWasm { balance: u64, } @@ -133,17 +135,18 @@ impl IdentityBalanceWasm { } #[wasm_bindgen(js_name = "IdentityBalanceEntry")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct IdentityBalanceEntryWasm { - identity_id: String, + identity_id: IdentifierWasm, balance: u64, } #[wasm_bindgen(js_class = IdentityBalanceEntry)] impl IdentityBalanceEntryWasm { #[wasm_bindgen(getter = "identityId")] - pub fn identity_id(&self) -> String { - self.identity_id.clone() + pub fn identity_id(&self) -> IdentifierWasm { + self.identity_id } #[wasm_bindgen(getter = "balance")] @@ -153,7 +156,8 @@ impl IdentityBalanceEntryWasm { } #[wasm_bindgen(js_name = "IdentityBalanceAndRevision")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct IdentityBalanceAndRevisionWasm { balance: u64, revision: u64, @@ -179,7 +183,8 @@ impl IdentityBalanceAndRevisionWasm { } #[wasm_bindgen(js_name = "IdentityNonce")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct IdentityNonceWasm { nonce: u64, } @@ -197,6 +202,11 @@ impl IdentityNonceWasm { BigInt::from(self.nonce) } } + +impl_wasm_serde_conversions!(IdentityBalanceWasm); +impl_wasm_serde_conversions!(IdentityBalanceEntryWasm); +impl_wasm_serde_conversions!(IdentityBalanceAndRevisionWasm); +impl_wasm_serde_conversions!(IdentityNonceWasm); #[wasm_bindgen(typescript_custom_section)] const IDENTITIES_CONTRACT_KEYS_QUERY_TS: &'static str = r#" /** @@ -423,7 +433,7 @@ struct IdentityKeysQueryParsed { } impl IdentityBalanceEntryWasm { - fn new(identity_id: String, balance: u64) -> Self { + fn new(identity_id: IdentifierWasm, balance: u64) -> Self { IdentityBalanceEntryWasm { identity_id, balance, @@ -484,10 +494,10 @@ impl WasmSdk { identity .map(|identity| { - ProofMetadataResponseWasm::from_parts( - JsValue::from(IdentityWasm::from(identity)), - metadata.into(), - proof.into(), + ProofMetadataResponseWasm::from_sdk_parts( + IdentityWasm::from(identity), + metadata, + proof, ) }) .ok_or_else(|| WasmSdkError::not_found("Identity not found")) @@ -732,9 +742,7 @@ impl WasmSdk { let data = IdentityNonceWasm::new(nonce); Ok(ProofMetadataResponseWasm::from_sdk_parts( - JsValue::from(data), - metadata, - proof, + data, metadata, proof, )) } @@ -805,9 +813,7 @@ impl WasmSdk { let data = IdentityNonceWasm::new(nonce); Ok(ProofMetadataResponseWasm::from_sdk_parts( - JsValue::from(data), - metadata, - proof, + data, metadata, proof, )) } @@ -863,9 +869,8 @@ impl WasmSdk { for identifier in identifiers { if let Some(Some(balance)) = balances_result.get(&identifier) { - let identity_id = IdentifierWasm::from(identifier).get_base58(); results_array.push(&JsValue::from(IdentityBalanceEntryWasm::new( - identity_id, + IdentifierWasm::from(identifier), *balance, ))); } @@ -1145,10 +1150,8 @@ impl WasmSdk { } } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(keys_array), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + keys_array, metadata, proof, )) } @@ -1176,7 +1179,7 @@ impl WasmSdk { balance_result .map(|balance| { ProofMetadataResponseWasm::from_sdk_parts( - JsValue::from(IdentityBalanceWasm::new(balance)), + IdentityBalanceWasm::new(balance), metadata, proof, ) @@ -1223,7 +1226,7 @@ impl WasmSdk { for identifier in identifiers { if let Some(Some(balance)) = balances_result.get(&identifier) { balances_array.push(&JsValue::from(IdentityBalanceEntryWasm::new( - IdentifierWasm::from(identifier).get_base58(), + IdentifierWasm::from(identifier), *balance, ))); } @@ -1260,7 +1263,7 @@ impl WasmSdk { result .map(|(balance, revision)| { ProofMetadataResponseWasm::from_sdk_parts( - JsValue::from(IdentityBalanceAndRevisionWasm::new(balance, revision)), + IdentityBalanceAndRevisionWasm::new(balance, revision), metadata, proof, ) @@ -1302,10 +1305,10 @@ impl WasmSdk { result .map(|identity| { - ProofMetadataResponseWasm::from_parts( - JsValue::from(IdentityWasm::from(identity)), - metadata.into(), - proof.into(), + ProofMetadataResponseWasm::from_sdk_parts( + IdentityWasm::from(identity), + metadata, + proof, ) }) .ok_or_else(|| WasmSdkError::not_found("Identity not found for public key hash")) diff --git a/packages/wasm-sdk/src/queries/mod.rs b/packages/wasm-sdk/src/queries/mod.rs index e9d6536be84..c487de72df0 100644 --- a/packages/wasm-sdk/src/queries/mod.rs +++ b/packages/wasm-sdk/src/queries/mod.rs @@ -12,18 +12,26 @@ pub mod voting; // Re-export all query functions for easy access pub use group::*; +use crate::impl_wasm_serde_conversions; +use crate::WasmSdkError; use js_sys::Uint8Array; +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +use wasm_dpp2::serialization::bytes_b64; +use wasm_dpp2::serialization::conversions as serialization; #[wasm_bindgen(js_name = "ResponseMetadata")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ResponseMetadataWasm { height: u64, core_chain_locked_height: u32, epoch: u32, time_ms: u64, protocol_version: u32, + #[serde(with = "bytes_b64")] chain_id: Vec, } @@ -83,6 +91,7 @@ impl ResponseMetadataWasm { self.chain_id = chain_id.to_vec(); } } +impl_wasm_serde_conversions!(ResponseMetadataWasm, ResponseMetadata); // Helper function to convert platform ResponseMetadata to our ResponseMetadata impl From for ResponseMetadataWasm { @@ -99,12 +108,17 @@ impl From for ResponseMetadataWasm } #[wasm_bindgen(js_name = "ProofInfo")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ProofInfoWasm { + #[serde(with = "bytes_b64")] grovedb_proof: Vec, + #[serde(with = "bytes_b64")] quorum_hash: Vec, + #[serde(with = "bytes_b64")] signature: Vec, round: u32, + #[serde(with = "bytes_b64")] block_id_hash: Vec, quorum_type: u32, } @@ -189,6 +203,7 @@ impl ProofInfoWasm { self.block_id_hash = block_id_hash.to_vec(); } } +impl_wasm_serde_conversions!(ProofInfoWasm, ProofInfo); // Helper function to convert platform Proof to our ProofInfo impl From for ProofInfoWasm { @@ -219,8 +234,27 @@ export type ProofMetadataResponseTyped = ProofMetadataResponse & { data: T }; #[wasm_bindgen(js_class = ProofMetadataResponse)] impl ProofMetadataResponseWasm { + fn to_serde(&self) -> Result { + Ok(ProofMetadataResponseSerde { + data: serialization::js_value_to_json(&self.data).map_err(WasmSdkError::from)?, + metadata: self.metadata.clone(), + proof: self.proof.clone(), + }) + } + + fn from_serde(serde: ProofMetadataResponseSerde) -> Result { + Ok(ProofMetadataResponseWasm { + data: serialization::json_to_js_value(&serde.data).map_err(WasmSdkError::from)?, + metadata: serde.metadata, + proof: serde.proof, + }) + } + #[wasm_bindgen(constructor)] pub fn new(data: JsValue, metadata: ResponseMetadataWasm, proof: ProofInfoWasm) -> Self { + // Store data as-is. Conversion to JSON happens in to_serde()/toJSON(). + // This allows WASM objects (like DataContractWasm) to be stored directly + // and their toJSON() method will be called when serializing. ProofMetadataResponseWasm { data, metadata, @@ -257,30 +291,56 @@ impl ProofMetadataResponseWasm { pub fn set_proof(&mut self, proof: ProofInfoWasm) { self.proof = proof; } -} -impl ProofMetadataResponseWasm { - pub(crate) fn from_parts( - data: JsValue, - metadata: ResponseMetadataWasm, - proof: ProofInfoWasm, - ) -> Self { - ProofMetadataResponseWasm { - data, - metadata, - proof, - } + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> Result { + let serde_value = self.to_serde()?; + serialization::to_json(&serde_value).map_err(WasmSdkError::from) } + #[wasm_bindgen(js_name = "fromJSON")] + pub fn from_json(js: JsValue) -> Result { + let serde_struct: ProofMetadataResponseSerde = + serialization::from_json(js).map_err(WasmSdkError::from)?; + ProofMetadataResponseWasm::from_serde(serde_struct) + } + + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> Result { + let serde_value = self.to_serde()?; + serialization::to_object(&serde_value).map_err(WasmSdkError::from) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(obj: JsValue) -> Result { + let serde_struct: ProofMetadataResponseSerde = + serialization::from_object(obj).map_err(WasmSdkError::from)?; + ProofMetadataResponseWasm::from_serde(serde_struct) + } +} + +impl ProofMetadataResponseWasm { + /// Create from SDK response parts with a WASM wrapper object or JsValue as data. + /// + /// Use this for WASM wrapper types (e.g., `DataContractWasm`, `IdentityWasm`) + /// or JS values (Map, Array, or pre-serialized JsValue). The data is stored as-is, + /// and when `toJSON()` is called, WASM objects will have their `toJSON()` method + /// invoked automatically. + /// + /// This allows `response.data` to return the actual WASM object with all its methods. pub(crate) fn from_sdk_parts( data: impl Into, metadata: dash_sdk::platform::proto::ResponseMetadata, proof: dash_sdk::platform::proto::Proof, ) -> Self { - ProofMetadataResponseWasm { - data: data.into(), - metadata: metadata.into(), - proof: proof.into(), - } + ProofMetadataResponseWasm::new(data.into(), metadata.into(), proof.into()) } } + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ProofMetadataResponseSerde { + data: JsonValue, + metadata: ResponseMetadataWasm, + proof: ProofInfoWasm, +} diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index 7fc9d00a8a2..39b13978743 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -1,13 +1,16 @@ use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore::hashes::{sha256d, Hash as _}; use js_sys::Map; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; #[wasm_bindgen(js_name = "ProtocolVersionUpgradeState")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ProtocolVersionUpgradeStateWasm { current_protocol_version: u32, next_protocol_version: Option, @@ -63,7 +66,8 @@ impl ProtocolVersionUpgradeStateWasm { } #[wasm_bindgen(js_name = "ProtocolVersionUpgradeVoteStatus")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ProtocolVersionUpgradeVoteStatusWasm { pro_tx_hash: String, version: u32, @@ -91,6 +95,9 @@ impl ProtocolVersionUpgradeVoteStatusWasm { } } +impl_wasm_serde_conversions!(ProtocolVersionUpgradeStateWasm); +impl_wasm_serde_conversions!(ProtocolVersionUpgradeVoteStatusWasm); + #[wasm_bindgen] impl WasmSdk { #[wasm_bindgen(js_name = "getProtocolVersionUpgradeState")] diff --git a/packages/wasm-sdk/src/queries/system.rs b/packages/wasm-sdk/src/queries/system.rs index e76cece65ea..95dd9f94e5b 100644 --- a/packages/wasm-sdk/src/queries/system.rs +++ b/packages/wasm-sdk/src/queries/system.rs @@ -1,15 +1,18 @@ use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::identifier_from_js; use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use dash_sdk::dpp::core_types::validator_set::v0::ValidatorSetV0Getters; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; use js_sys::{Array, BigInt}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; +use wasm_dpp2::identifier::IdentifierWasm; #[wasm_bindgen(js_name = "StatusSoftware")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusSoftwareWasm { #[wasm_bindgen(getter_with_clone)] pub dapi: String, @@ -30,7 +33,8 @@ impl StatusSoftwareWasm { } #[wasm_bindgen(js_name = "StatusTenderdashProtocol")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusTenderdashProtocolWasm { #[wasm_bindgen(getter_with_clone)] pub p2p: u32, @@ -45,7 +49,8 @@ impl StatusTenderdashProtocolWasm { } #[wasm_bindgen(js_name = "StatusDriveProtocol")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusDriveProtocolWasm { #[wasm_bindgen(getter_with_clone)] pub latest: u32, @@ -60,7 +65,8 @@ impl StatusDriveProtocolWasm { } #[wasm_bindgen(js_name = "StatusProtocol")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusProtocolWasm { #[wasm_bindgen(getter_with_clone)] pub tenderdash: StatusTenderdashProtocolWasm, @@ -75,7 +81,8 @@ impl StatusProtocolWasm { } #[wasm_bindgen(js_name = "StatusVersion")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusVersionWasm { #[wasm_bindgen(getter_with_clone)] pub software: StatusSoftwareWasm, @@ -90,7 +97,8 @@ impl StatusVersionWasm { } #[wasm_bindgen(js_name = "StatusNode")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusNodeWasm { #[wasm_bindgen(getter_with_clone)] pub id: String, @@ -105,7 +113,8 @@ impl StatusNodeWasm { } #[wasm_bindgen(js_name = "StatusChain")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusChainWasm { #[wasm_bindgen(getter_with_clone)] pub catching_up: bool, @@ -155,7 +164,8 @@ impl StatusChainWasm { } #[wasm_bindgen(js_name = "StatusNetwork")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusNetworkWasm { #[wasm_bindgen(getter_with_clone)] pub chain_id: String, @@ -176,7 +186,8 @@ impl StatusNetworkWasm { } #[wasm_bindgen(js_name = "StatusStateSync")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusStateSyncWasm { #[wasm_bindgen(getter_with_clone)] pub total_synced_time: String, @@ -197,7 +208,8 @@ pub struct StatusStateSyncWasm { } #[wasm_bindgen(js_name = "StatusTime")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusTimeWasm { #[wasm_bindgen(getter_with_clone)] pub local: String, @@ -226,7 +238,8 @@ impl StatusTimeWasm { } #[wasm_bindgen(js_name = "StatusResponse")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StatusResponseWasm { #[wasm_bindgen(getter_with_clone)] pub version: StatusVersionWasm, @@ -262,8 +275,26 @@ impl StatusResponseWasm { } } +impl_wasm_serde_conversions!(StatusSoftwareWasm); +impl_wasm_serde_conversions!(StatusTenderdashProtocolWasm); +impl_wasm_serde_conversions!(StatusDriveProtocolWasm); +impl_wasm_serde_conversions!(StatusProtocolWasm); +impl_wasm_serde_conversions!(StatusVersionWasm); +impl_wasm_serde_conversions!(StatusNodeWasm); +impl_wasm_serde_conversions!(StatusChainWasm); +impl_wasm_serde_conversions!(StatusNetworkWasm); +impl_wasm_serde_conversions!(StatusStateSyncWasm); +impl_wasm_serde_conversions!(StatusTimeWasm); +impl_wasm_serde_conversions!(StatusResponseWasm); +impl_wasm_serde_conversions!(QuorumInfoWasm); +impl_wasm_serde_conversions!(CurrentQuorumsInfoWasm); +impl_wasm_serde_conversions!(PrefundedSpecializedBalanceWasm); +impl_wasm_serde_conversions!(PathElementWasm); +impl_wasm_serde_conversions!(StateTransitionResultWasm); + #[wasm_bindgen(js_name = "QuorumInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct QuorumInfoWasm { quorum_hash: String, quorum_type: String, @@ -319,7 +350,8 @@ impl QuorumInfoWasm { } #[wasm_bindgen(js_name = "CurrentQuorumsInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct CurrentQuorumsInfoWasm { quorums: Vec, height: u64, @@ -349,15 +381,15 @@ impl CurrentQuorumsInfoWasm { } #[wasm_bindgen(js_name = "PrefundedSpecializedBalance")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct PrefundedSpecializedBalanceWasm { - #[wasm_bindgen(getter_with_clone)] - pub identity_id: String, + identity_id: IdentifierWasm, balance: u64, } impl PrefundedSpecializedBalanceWasm { - fn new(identity_id: String, balance: u64) -> Self { + fn new(identity_id: IdentifierWasm, balance: u64) -> Self { Self { identity_id, balance, @@ -367,6 +399,11 @@ impl PrefundedSpecializedBalanceWasm { #[wasm_bindgen(js_class = PrefundedSpecializedBalance)] impl PrefundedSpecializedBalanceWasm { + #[wasm_bindgen(getter = "identityId")] + pub fn identity_id(&self) -> IdentifierWasm { + self.identity_id + } + #[wasm_bindgen(getter = "balance")] pub fn balance(&self) -> BigInt { BigInt::from(self.balance) @@ -374,7 +411,8 @@ impl PrefundedSpecializedBalanceWasm { } #[wasm_bindgen(js_name = "PathElement")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct PathElementWasm { path: Vec, value: Option, @@ -404,7 +442,8 @@ impl PathElementWasm { } #[wasm_bindgen(js_name = "StateTransitionResult")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StateTransitionResultWasm { #[wasm_bindgen(getter_with_clone)] pub state_transition_hash: String, @@ -765,7 +804,6 @@ impl WasmSdk { use drive_proof_verifier::types::PrefundedSpecializedBalance as PrefundedBalance; let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; - let identity_id_base58 = identity_identifier.to_string(Encoding::Base58); // Fetch prefunded specialized balance let balance_result = PrefundedBalance::fetch(self.as_ref(), identity_identifier).await?; @@ -773,7 +811,7 @@ impl WasmSdk { let balance_value = balance_result.map(|b| b.0).unwrap_or(0); Ok(PrefundedSpecializedBalanceWasm::new( - identity_id_base58, + IdentifierWasm::from(identity_identifier), balance_value, )) } @@ -947,7 +985,6 @@ impl WasmSdk { use drive_proof_verifier::types::PrefundedSpecializedBalance as PrefundedBalance; let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; - let identity_id_base58 = identity_identifier.to_string(Encoding::Base58); // Fetch prefunded specialized balance with proof let (balance_result, metadata, proof) = PrefundedBalance::fetch_with_metadata_and_proof( @@ -960,7 +997,7 @@ impl WasmSdk { let data = balance_result .map(|balance| { JsValue::from(PrefundedSpecializedBalanceWasm::new( - identity_id_base58.clone(), + IdentifierWasm::from(identity_identifier), balance.0, )) }) diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index 3c06aac02a2..44955912509 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -1,4 +1,5 @@ use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::{identifier_from_js, identifiers_from_js}; use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; @@ -9,13 +10,15 @@ use dash_sdk::dpp::tokens::status::TokenStatus; use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; use dash_sdk::platform::{FetchMany, Identifier}; use js_sys::{BigInt, Map}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; use wasm_dpp2::identifier::IdentifierWasm; use wasm_dpp2::tokens::{IdentityTokenInfoWasm, TokenContractInfoWasm, TokenStatusWasm}; #[wasm_bindgen(js_name = "TokenPriceInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct TokenPriceInfoWasm { token_id: IdentifierWasm, current_price: String, @@ -51,7 +54,8 @@ impl TokenPriceInfoWasm { } #[wasm_bindgen(js_name = "TokenLastClaim")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct TokenLastClaimWasm { last_claim_timestamp_ms: u64, last_claim_block_height: u64, @@ -78,9 +82,11 @@ impl TokenLastClaimWasm { self.last_claim_block_height } } +impl_wasm_serde_conversions!(TokenLastClaimWasm); #[wasm_bindgen(js_name = "TokenTotalSupply")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct TokenTotalSupplyWasm { total_supply: u64, } @@ -99,6 +105,9 @@ impl TokenTotalSupplyWasm { } } +impl_wasm_serde_conversions!(TokenTotalSupplyWasm); +impl_wasm_serde_conversions!(TokenPriceInfoWasm); + #[wasm_bindgen] impl WasmSdk { /// Calculate token ID from contract ID and token position @@ -160,7 +169,7 @@ impl WasmSdk { /// "Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv", /// 0 /// ); - /// console.log(`Token ${priceInfo.tokenId.base58()} current price: ${priceInfo.currentPrice}`); + /// console.log(`Token ${priceInfo.tokenId.toBase58()} current price: ${priceInfo.currentPrice}`); /// ``` #[wasm_bindgen(js_name = "getTokenPriceByContract")] pub async fn get_token_price_by_contract( @@ -215,14 +224,14 @@ impl WasmSdk { } else { Err(WasmSdkError::not_found(format!( "No pricing schedule found for token at contract {} position {}", - IdentifierWasm::from(contract_identifier).get_base58(), + IdentifierWasm::from(contract_identifier).to_base58(), token_position ))) } } else { Err(WasmSdkError::not_found(format!( "Token not found at contract {} position {}", - IdentifierWasm::from(contract_identifier).get_base58(), + IdentifierWasm::from(contract_identifier).to_base58(), token_position ))) } diff --git a/packages/wasm-sdk/src/queries/utils.rs b/packages/wasm-sdk/src/queries/utils.rs index 4fbdcd3e8a3..02f8b48d1c9 100644 --- a/packages/wasm-sdk/src/queries/utils.rs +++ b/packages/wasm-sdk/src/queries/utils.rs @@ -1,5 +1,6 @@ use dash_sdk::dpp::platform_value::{Identifier, Value as PlatformValue}; use serde::de::DeserializeOwned; +use serde::Serialize; use serde_json::Value as JsonValue; use wasm_bindgen::JsValue; use wasm_dpp2::identifier::IdentifierWasm; @@ -74,7 +75,9 @@ pub(crate) fn convert_json_values_to_platform_values( .unwrap_or_default() .into_iter() .map(|value| { - serde_wasm_bindgen::to_value(&value).map_err(|err| { + // Use json_compatible() to ensure objects become plain JS objects (not Maps) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + value.serialize(&serializer).map_err(|err| { WasmSdkError::invalid_argument(format!("Invalid {} entry: {}", field_name, err)) }) }) diff --git a/packages/wasm-sdk/src/queries/voting/resources.rs b/packages/wasm-sdk/src/queries/voting/resources.rs index 7c3316987fb..d7d12462994 100644 --- a/packages/wasm-sdk/src/queries/voting/resources.rs +++ b/packages/wasm-sdk/src/queries/voting/resources.rs @@ -10,7 +10,7 @@ use dash_sdk::platform::FetchMany; use drive::query::vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery; use drive_proof_verifier::types::{ContestedResource, ContestedResources}; use js_sys::{Array, Reflect}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; use wasm_dpp2::identifier::IdentifierWasm; @@ -152,7 +152,9 @@ fn contested_resources_into_wasm( let array = Array::new(); for resource in contested_resources.0 { - let js_value = serde_wasm_bindgen::to_value(&resource.0).map_err(|e| { + // Use json_compatible() to ensure objects become plain JS objects (not Maps) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + let js_value = resource.0.serialize(&serializer).map_err(|e| { WasmSdkError::serialization(format!( "Failed to serialize contested resource value: {}", e diff --git a/packages/wasm-sdk/src/queries/voting/voters.rs b/packages/wasm-sdk/src/queries/voting/voters.rs index 616356ecb97..a6e6badf5aa 100644 --- a/packages/wasm-sdk/src/queries/voting/voters.rs +++ b/packages/wasm-sdk/src/queries/voting/voters.rs @@ -9,7 +9,7 @@ use dash_sdk::platform::FetchMany; use drive::query::vote_poll_contestant_votes_query::ContestedDocumentVotePollVotesDriveQuery; use drive_proof_verifier::types::Voter; use js_sys::Array; -use platform_value::{string_encoding::Encoding, Identifier}; +use platform_value::Identifier; use serde::Deserialize; use serde_json::Value as JsonValue; use wasm_bindgen::prelude::wasm_bindgen; @@ -163,7 +163,7 @@ fn parse_contested_resource_voters_query( impl WasmSdk { #[wasm_bindgen( js_name = "getContestedResourceVotersForIdentity", - unchecked_return_type = "Array" + unchecked_return_type = "Array" )] pub async fn get_contested_resource_voters_for_identity( &self, @@ -177,8 +177,7 @@ impl WasmSdk { let array = Array::new(); for voter in voters.0.into_iter() { - let identifier_js = IdentifierWasm::from(voter.0); - array.push(&JsValue::from(identifier_js)); + array.push(&JsValue::from(IdentifierWasm::from(voter.0))); } Ok(array) @@ -186,7 +185,7 @@ impl WasmSdk { #[wasm_bindgen( js_name = "getContestedResourceVotersForIdentityWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped>" + unchecked_return_type = "ProofMetadataResponseTyped>" )] pub async fn get_contested_resource_voters_for_identity_with_proof_info( &self, @@ -197,21 +196,15 @@ impl WasmSdk { let (voters, metadata, proof) = Voter::fetch_many_with_metadata_and_proof(self.as_ref(), drive_query, None).await?; - let voters_list: Vec = voters - .0 - .into_iter() - .map(|voter| voter.0.to_string(Encoding::Base58)) - .collect(); - - let data = serde_wasm_bindgen::to_value(&voters_list).map_err(|e| { - WasmSdkError::serialization(format!( - "Failed to serialize contested resource voters: {}", - e - )) - })?; + let voters_array = Array::new(); + for voter in voters.0.into_iter() { + voters_array.push(&JsValue::from(IdentifierWasm::from(voter.0))); + } Ok(ProofMetadataResponseWasm::from_sdk_parts( - data, metadata, proof, + voters_array, + metadata, + proof, )) } } diff --git a/packages/wasm-sdk/src/queries/voting/votes.rs b/packages/wasm-sdk/src/queries/voting/votes.rs index b73a30b1881..b61ae2b40f1 100644 --- a/packages/wasm-sdk/src/queries/voting/votes.rs +++ b/packages/wasm-sdk/src/queries/voting/votes.rs @@ -3,18 +3,16 @@ use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use crate::WasmSdkError; use dash_sdk::dpp::platform_value::Identifier; -use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; -use dash_sdk::dpp::voting::vote_polls::VotePoll; -use dash_sdk::dpp::voting::votes::resource_vote::v0::ResourceVoteV0; use dash_sdk::dpp::voting::votes::resource_vote::ResourceVote; use dash_sdk::platform::FetchMany; use drive::query::contested_resource_votes_given_by_identity_query::ContestedResourceVotesGivenByIdentityQuery; use drive_proof_verifier::types::ResourceVotesByIdentity; -use js_sys::Array; -use platform_value::string_encoding::Encoding; +use js_sys::Map; use serde::Deserialize; use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; use wasm_dpp2::identifier::IdentifierWasm; +use wasm_dpp2::voting::resource_vote::ResourceVoteWasm; #[wasm_bindgen(typescript_custom_section)] const CONTESTED_RESOURCE_IDENTITY_VOTES_QUERY_TS: &'static str = r#" @@ -128,100 +126,44 @@ fn parse_contested_resource_identity_votes_query( build_contested_resource_identity_votes_query(input) } -fn resource_votes_to_json( - votes: ResourceVotesByIdentity, -) -> Result, WasmSdkError> { - let mut results = Vec::new(); +/// Convert ResourceVotesByIdentity to a Map. +fn resource_votes_to_map(votes: ResourceVotesByIdentity) -> Map { + let map = Map::new(); for (vote_id, vote_opt) in votes.into_iter() { let Some(vote) = vote_opt else { continue; }; - let ResourceVote::V0(ResourceVoteV0 { - vote_poll, - resource_vote_choice, - }) = vote; - - let VotePoll::ContestedDocumentResourceVotePoll(vote_poll) = vote_poll; - - let poll_unique_id = vote_poll.unique_id().map_err(|e| { - WasmSdkError::serialization(format!("Failed to derive vote poll unique id: {}", e)) - })?; - - let index_values_json = vote_poll - .index_values - .into_iter() - .map(|value| { - serde_json::to_value(value).map_err(|e| { - WasmSdkError::serialization(format!( - "Failed to serialize vote poll index value: {}", - e - )) - }) - }) - .collect::, WasmSdkError>>()?; - - let poll_json = serde_json::json!({ - "type": "contestedDocumentResource", - "uniqueId": poll_unique_id.to_string(Encoding::Base58), - "contractId": vote_poll.contract_id.to_string(Encoding::Base58), - "documentTypeName": vote_poll.document_type_name, - "indexName": vote_poll.index_name, - "indexValues": index_values_json, - }); - - let choice_json = match resource_vote_choice { - ResourceVoteChoice::TowardsIdentity(identifier) => serde_json::json!({ - "type": "towardsIdentity", - "identityId": identifier.to_string(Encoding::Base58), - }), - ResourceVoteChoice::Abstain => serde_json::json!({ "type": "abstain" }), - ResourceVoteChoice::Lock => serde_json::json!({ "type": "lock" }), - }; + let key = JsValue::from(IdentifierWasm::from(vote_id)); + let value = JsValue::from(ResourceVoteWasm::from(vote)); - results.push(serde_json::json!({ - "voteId": vote_id.to_string(Encoding::Base58), - "votePoll": poll_json, - "choice": choice_json, - })); + map.set(&key, &value); } - Ok(results) + map } #[wasm_bindgen] impl WasmSdk { #[wasm_bindgen( js_name = "getContestedResourceIdentityVotes", - unchecked_return_type = "Array" + unchecked_return_type = "Map" )] pub async fn get_contested_resource_identity_votes( &self, query: ContestedResourceIdentityVotesQueryJs, - ) -> Result { + ) -> Result { let drive_query = parse_contested_resource_identity_votes_query(query)?; let votes = ResourceVote::fetch_many(self.as_ref(), drive_query).await?; - let votes_json = resource_votes_to_json(votes)?; - let array = Array::new(); - for vote in votes_json { - let js_value = serde_wasm_bindgen::to_value(&vote).map_err(|e| { - WasmSdkError::serialization(format!( - "Failed to serialize contested resource identity vote: {}", - e - )) - })?; - array.push(&js_value); - } - - Ok(array) + Ok(resource_votes_to_map(votes)) } #[wasm_bindgen( js_name = "getContestedResourceIdentityVotesWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped<{ votes: Array }>" + unchecked_return_type = "ProofMetadataResponseTyped>" )] pub async fn get_contested_resource_identity_votes_with_proof_info( &self, @@ -232,20 +174,10 @@ impl WasmSdk { ResourceVote::fetch_many_with_metadata_and_proof(self.as_ref(), drive_query, None) .await?; - let votes_json = resource_votes_to_json(votes)?; - - let data = serde_wasm_bindgen::to_value(&serde_json::json!({ - "votes": votes_json - })) - .map_err(|e| { - WasmSdkError::serialization(format!( - "Failed to serialize contested resource identity votes response: {}", - e - )) - })?; + let votes_map = resource_votes_to_map(votes); Ok(ProofMetadataResponseWasm::from_sdk_parts( - data, metadata, proof, + votes_map, metadata, proof, )) } } diff --git a/packages/wasm-sdk/src/serialization.rs b/packages/wasm-sdk/src/serialization.rs new file mode 100644 index 00000000000..9935b921eef --- /dev/null +++ b/packages/wasm-sdk/src/serialization.rs @@ -0,0 +1,71 @@ +// Re-export the macro from wasm-dpp2 for newtype wrappers +pub use wasm_dpp2::impl_wasm_conversions; + +/// Macro to implement `toObject`, `fromObject`, `toJSON`, and `fromJSON` methods +/// for a wasm_bindgen type using serde_format. +/// +/// This macro is for types that directly implement Serialize/Deserialize. +/// For newtype wrappers (e.g., `struct Foo(Inner)`), use `impl_wasm_conversions!` instead. +/// +/// # Usage +/// +/// ```ignore +/// // Single-argument: uses Rust type name as JS class +/// impl_wasm_serde_conversions!(MyTypeWasm); +/// +/// // Two-argument: specify JS class name +/// impl_wasm_serde_conversions!(MyTypeWasm, MyType); +/// ``` +#[macro_export] +macro_rules! impl_wasm_serde_conversions { + // Single-argument form: Rust type name equals JS class name + ($ty:ty) => { + #[wasm_bindgen::prelude::wasm_bindgen] + impl $ty { + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> Result { + wasm_dpp2::serialization::to_object(self).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromObject)] + pub fn from_object(obj: wasm_bindgen::JsValue) -> Result<$ty, $crate::WasmSdkError> { + wasm_dpp2::serialization::from_object(obj).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + wasm_dpp2::serialization::to_json(self).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromJSON)] + pub fn from_json(js: wasm_bindgen::JsValue) -> Result<$ty, $crate::WasmSdkError> { + wasm_dpp2::serialization::from_json(js).map_err($crate::WasmSdkError::from) + } + } + }; + // Two-argument form: Rust type and JS class name + ($ty:ty, $js_class:ident) => { + #[wasm_bindgen::prelude::wasm_bindgen(js_class = $js_class)] + impl $ty { + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> Result { + wasm_dpp2::serialization::to_object(self).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromObject)] + pub fn from_object(obj: wasm_bindgen::JsValue) -> Result<$ty, $crate::WasmSdkError> { + wasm_dpp2::serialization::from_object(obj).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + wasm_dpp2::serialization::to_json(self).map_err($crate::WasmSdkError::from) + } + + #[wasm_bindgen::prelude::wasm_bindgen(js_name = fromJSON)] + pub fn from_json(js: wasm_bindgen::JsValue) -> Result<$ty, $crate::WasmSdkError> { + wasm_dpp2::serialization::from_json(js).map_err($crate::WasmSdkError::from) + } + } + }; +} diff --git a/packages/wasm-sdk/src/state_transitions/documents/mod.rs b/packages/wasm-sdk/src/state_transitions/documents/mod.rs index e502e409fb5..bf12acd5d00 100644 --- a/packages/wasm-sdk/src/state_transitions/documents/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/documents/mod.rs @@ -25,6 +25,7 @@ use dash_sdk::dpp::state_transition::StateTransition; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::Fetch; use js_sys; +use serde::Serialize; use serde_json; use simple_signer::SingleKeySigner; use wasm_bindgen::prelude::*; @@ -431,8 +432,10 @@ impl WasmSdk { for (key, value) in properties { // Convert platform Value to JSON value first, then to JsValue + // Use json_compatible() to ensure objects become plain JS objects (not Maps) if let Ok(json_value) = serde_json::to_value(value) { - if let Ok(js_value) = serde_wasm_bindgen::to_value(&json_value) { + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + if let Ok(js_value) = json_value.serialize(&serializer) { js_sys::Reflect::set( &data_obj, &JsValue::from_str(key), @@ -753,8 +756,10 @@ impl WasmSdk { for (key, value) in properties { // Convert platform Value to JSON value first, then to JsValue + // Use json_compatible() to ensure objects become plain JS objects (not Maps) if let Ok(json_value) = serde_json::to_value(value) { - if let Ok(js_value) = serde_wasm_bindgen::to_value(&json_value) { + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + if let Ok(js_value) = json_value.serialize(&serializer) { js_sys::Reflect::set( &data_obj, &JsValue::from_str(key), diff --git a/packages/wasm-sdk/src/state_transitions/tokens/mod.rs b/packages/wasm-sdk/src/state_transitions/tokens/mod.rs index 55778fe300f..0bf5aaaa21a 100644 --- a/packages/wasm-sdk/src/state_transitions/tokens/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/tokens/mod.rs @@ -15,11 +15,17 @@ use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; use dash_sdk::dpp::tokens::calculate_token_id; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::Fetch; +use serde::Serialize; use serde_json; -use serde_wasm_bindgen::to_value; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +/// Helper function to serialize to JsValue using json_compatible() +fn to_json_compatible(value: &T) -> Result { + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + value.serialize(&serializer) +} + // WasmSigner has been replaced with SingleKeySigner from simple-signer crate // Helper functions for token operations @@ -89,7 +95,7 @@ impl WasmSdk { ) -> Result { match proof_result { StateTransitionProofResult::VerifiedTokenBalance(recipient_id, new_balance) => { - to_value(&serde_json::json!({ + to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenBalance", "recipientId": recipient_id.to_string(Encoding::Base58), "newBalance": new_balance.to_string() @@ -99,7 +105,7 @@ impl WasmSdk { }) } StateTransitionProofResult::VerifiedTokenActionWithDocument(doc) => { - to_value(&serde_json::json!({ + to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenActionWithDocument", "documentId": doc.id().to_string(Encoding::Base58), "message": "Token operation recorded successfully" @@ -109,7 +115,7 @@ impl WasmSdk { }) } StateTransitionProofResult::VerifiedTokenGroupActionWithDocument(power, doc) => { - to_value(&serde_json::json!({ + to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenGroupActionWithDocument", "groupPower": power, "document": doc.is_some() @@ -122,7 +128,7 @@ impl WasmSdk { power, status, balance, - ) => to_value(&serde_json::json!({ + ) => to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenGroupActionWithTokenBalance", "groupPower": power, "status": format!("{:?}", status), @@ -858,7 +864,7 @@ impl WasmSdk { // Format and return result based on the proof result type match proof_result { StateTransitionProofResult::VerifiedTokenPricingSchedule(owner_id, schedule) => { - to_value(&serde_json::json!({ + to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenPricingSchedule", "ownerId": owner_id.to_string(Encoding::Base58), "pricingSchedule": schedule.map(|s| match s { @@ -886,7 +892,7 @@ impl WasmSdk { power, status, schedule, - ) => to_value(&serde_json::json!({ + ) => to_json_compatible(&serde_json::json!({ "type": "VerifiedTokenGroupActionWithTokenPricingSchedule", "groupPower": power, "status": format!("{:?}", status), diff --git a/packages/wasm-sdk/src/utils.rs b/packages/wasm-sdk/src/utils.rs index 4d00b930c26..caed879cc9e 100644 --- a/packages/wasm-sdk/src/utils.rs +++ b/packages/wasm-sdk/src/utils.rs @@ -1,11 +1,13 @@ use crate::WasmSdkError; use platform_value::Value; use wasm_bindgen::JsValue; -use wasm_dpp2::utils::ToSerdeJSONExt; +use wasm_dpp2::serialization::conversions; /// Convert a `JsValue` coming from JavaScript into a Platform `Value`. +/// +/// Uses object-format conversion which properly handles BigInt and Uint8Array. pub fn js_value_to_platform_value(value: JsValue) -> Result { - value.with_serde_to_platform_value().map_err(|err| { + conversions::js_value_to_platform_value(&value).map_err(|err| { WasmSdkError::invalid_argument(format!( "Failed to convert JS value to platform value: {err}" )) diff --git a/packages/wasm-sdk/src/wallet/extended_derivation.rs b/packages/wasm-sdk/src/wallet/extended_derivation.rs index 8d1ac323fc7..b2dea07112e 100644 --- a/packages/wasm-sdk/src/wallet/extended_derivation.rs +++ b/packages/wasm-sdk/src/wallet/extended_derivation.rs @@ -3,11 +3,13 @@ //! Implements 256-bit derivation paths for DashPay contact keys use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::deserialize_required_query; use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore; use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::key_wallet::{bip32, DerivationPath, ExtendedPrivKey}; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use tracing::debug; use wasm_bindgen::prelude::*; @@ -132,7 +134,8 @@ fn derive_common_from_mnemonic( } #[wasm_bindgen(js_name = "DerivedKeyInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct DerivedKeyInfoWasm { #[wasm_bindgen(getter_with_clone)] pub path: String, @@ -168,9 +171,11 @@ impl From for DerivedKeyInfoWasm { } // Field getters are generated via getter_with_clone annotations above +impl_wasm_serde_conversions!(DerivedKeyInfoWasm); #[wasm_bindgen(js_name = "DashpayContactKeyInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct DashpayContactKeyInfoWasm { // common #[wasm_bindgen(getter_with_clone)] @@ -232,6 +237,7 @@ impl DashpayContactKeyInfoWasm { } // Field getters are generated via getter_with_clone annotations above +impl_wasm_serde_conversions!(DashpayContactKeyInfoWasm); #[wasm_bindgen] impl WasmSdk { /// Derive a key from seed phrase with extended path supporting 256-bit indices diff --git a/packages/wasm-sdk/src/wallet/key_derivation.rs b/packages/wasm-sdk/src/wallet/key_derivation.rs index c91399f4d6b..5d69f488002 100644 --- a/packages/wasm-sdk/src/wallet/key_derivation.rs +++ b/packages/wasm-sdk/src/wallet/key_derivation.rs @@ -3,6 +3,7 @@ //! Implements BIP32, BIP39, and BIP44 standards for hierarchical deterministic key derivation use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::queries::utils::{deserialize_query_with_default, deserialize_required_query}; use crate::sdk::WasmSdk; use bip39::{Language, Mnemonic}; @@ -198,7 +199,8 @@ impl DerivationPath { } #[wasm_bindgen(js_name = "DerivationPathInfo", getter_with_clone)] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct DerivationPathWasm { pub path: String, pub purpose: u32, @@ -223,7 +225,8 @@ impl From for DerivationPathWasm { } #[wasm_bindgen(getter_with_clone, js_name = "Dip13DerivationPathInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Dip13DerivationPathWasm { pub path: String, pub purpose: u32, @@ -234,7 +237,8 @@ pub struct Dip13DerivationPathWasm { } #[wasm_bindgen(getter_with_clone, js_name = "SeedPhraseKeyInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SeedPhraseKeyInfoWasm { #[wasm_bindgen(js_name = "privateKeyWif")] pub private_key_wif: String, @@ -247,7 +251,8 @@ pub struct SeedPhraseKeyInfoWasm { } #[wasm_bindgen(getter_with_clone, js_name = "PathDerivedKeyInfo")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct PathDerivedKeyInfoWasm { pub path: String, #[wasm_bindgen(js_name = "privateKeyWif")] @@ -260,6 +265,11 @@ pub struct PathDerivedKeyInfoWasm { pub network: String, } +impl_wasm_serde_conversions!(DerivationPathWasm); +impl_wasm_serde_conversions!(Dip13DerivationPathWasm); +impl_wasm_serde_conversions!(SeedPhraseKeyInfoWasm); +impl_wasm_serde_conversions!(PathDerivedKeyInfoWasm); + /// HD Key information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HDKeyInfo { diff --git a/packages/wasm-sdk/src/wallet/key_generation.rs b/packages/wasm-sdk/src/wallet/key_generation.rs index f453a4e09df..6cad4f3ed4f 100644 --- a/packages/wasm-sdk/src/wallet/key_generation.rs +++ b/packages/wasm-sdk/src/wallet/key_generation.rs @@ -3,15 +3,18 @@ //! Provides key generation and address derivation without full HD wallet support use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore::hashes::{sha256, Hash}; use dash_sdk::dpp::dashcore::secp256k1::{Secp256k1, SecretKey}; use dash_sdk::dpp::dashcore::{Address, Network, PrivateKey, PublicKey}; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use wasm_bindgen::prelude::*; /// Key pair information -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct KeyPair { /// Private key in WIF format pub private_key_wif: String, @@ -26,7 +29,8 @@ pub struct KeyPair { } #[wasm_bindgen(js_name = "KeyPair")] -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct KeyPairWasm { #[wasm_bindgen(getter_with_clone, js_name = "privateKeyWif")] pub private_key_wif: String, @@ -52,6 +56,8 @@ impl From for KeyPairWasm { } } +impl_wasm_serde_conversions!(KeyPairWasm); + #[wasm_bindgen] impl WasmSdk { fn build_key_pair( diff --git a/packages/wasm-sdk/tests/.eslintrc.yml b/packages/wasm-sdk/tests/.eslintrc.yml index 509d508a284..7dfe891dcb5 100644 --- a/packages/wasm-sdk/tests/.eslintrc.yml +++ b/packages/wasm-sdk/tests/.eslintrc.yml @@ -5,6 +5,8 @@ env: es2020: true node: true mocha: true +parserOptions: + ecmaVersion: 2024 rules: eol-last: - error diff --git a/packages/wasm-sdk/tests/functional/identities.spec.mjs b/packages/wasm-sdk/tests/functional/identities.spec.mjs index 02e4c5f93ae..8ddf4821290 100644 --- a/packages/wasm-sdk/tests/functional/identities.spec.mjs +++ b/packages/wasm-sdk/tests/functional/identities.spec.mjs @@ -25,6 +25,97 @@ describe('Identity queries', function describeBlock() { expect(r).to.be.ok(); }); + it('fetches identity with proof and toJSON returns full structure', async () => { + const response = await client.getIdentityWithProofInfo(TEST_IDENTITY); + + // Basic shape check + expect(response).to.be.ok(); + expect(response.data).to.be.ok(); + expect(response.metadata).to.be.ok(); + expect(response.proof).to.be.ok(); + + // Call toJSON and verify structure + const json = response.toJSON(); + + // Verify top-level structure + expect(json).to.have.property('data'); + expect(json).to.have.property('metadata'); + expect(json).to.have.property('proof'); + + // Verify identity data - id should be Base58 string in JSON + expect(json.data).to.have.property('id'); + expect(json.data.id).to.be.a('string'); + expect(json.data.id).to.equal(TEST_IDENTITY); + expect(json.data).to.have.property('balance'); + expect(json.data).to.have.property('revision'); + expect(json.data).to.have.property('publicKeys'); + expect(json.data.publicKeys).to.be.an('array'); + + // Verify metadata structure + expect(json.metadata).to.have.property('height'); + expect(json.metadata).to.have.property('coreChainLockedHeight'); + expect(json.metadata).to.have.property('epoch'); + expect(json.metadata).to.have.property('timeMs'); + expect(json.metadata).to.have.property('protocolVersion'); + expect(json.metadata).to.have.property('chainId'); + expect(json.metadata.chainId).to.be.a('string'); // base64 + + // Verify proof structure + expect(json.proof).to.have.property('grovedbProof'); + expect(json.proof.grovedbProof).to.be.a('string'); // base64 + expect(json.proof).to.have.property('quorumHash'); + expect(json.proof).to.have.property('signature'); + expect(json.proof).to.have.property('round'); + expect(json.proof).to.have.property('blockIdHash'); + expect(json.proof).to.have.property('quorumType'); + }); + + it('fetches data contract with proof and toJSON returns full structure', async () => { + const response = await client.getDataContractWithProofInfo(DPNS_CONTRACT); + + // Basic shape check + expect(response).to.be.ok(); + expect(response.data).to.be.ok(); + expect(response.metadata).to.be.ok(); + expect(response.proof).to.be.ok(); + + // Call toJSON and verify structure + const json = response.toJSON(); + + // Verify top-level structure + expect(json).to.have.property('data'); + expect(json).to.have.property('metadata'); + expect(json).to.have.property('proof'); + + // Verify data contract data - id should be Base58 string in JSON + expect(json.data).to.have.property('id'); + expect(json.data.id).to.be.a('string'); + expect(json.data.id).to.equal(DPNS_CONTRACT); + expect(json.data).to.have.property('ownerId'); + expect(json.data.ownerId).to.be.a('string'); + expect(json.data).to.have.property('version'); + expect(json.data).to.have.property('documentSchemas'); + expect(json.data.documentSchemas).to.be.an('object'); + + // Verify metadata structure + expect(json.metadata).to.have.property('height'); + expect(json.metadata).to.have.property('coreChainLockedHeight'); + expect(json.metadata).to.have.property('epoch'); + expect(json.metadata).to.have.property('timeMs'); + expect(json.metadata).to.have.property('protocolVersion'); + expect(json.metadata).to.have.property('chainId'); + expect(json.metadata.chainId).to.be.a('string'); // base64 + + // Verify proof structure + expect(json.proof).to.have.property('grovedbProof'); + expect(json.proof.grovedbProof).to.be.a('string'); // base64 + expect(json.proof).to.have.property('quorumHash'); + expect(json.proof).to.have.property('signature'); + expect(json.proof).to.have.property('round'); + expect(json.proof).to.have.property('blockIdHash'); + expect(json.proof).to.have.property('quorumType'); + }); + it('gets identity balance and nonce', async () => { const bal = await client.getIdentityBalance(TEST_IDENTITY); expect(bal).to.be.an('object'); diff --git a/packages/wasm-sdk/tests/unit/conversion.spec.mjs b/packages/wasm-sdk/tests/unit/conversion.spec.mjs new file mode 100644 index 00000000000..c8bf52e5d4f --- /dev/null +++ b/packages/wasm-sdk/tests/unit/conversion.spec.mjs @@ -0,0 +1,223 @@ +import init, * as sdk from '../../dist/sdk.compressed.js'; + +describe('serde conversions (unit)', () => { + before(async () => { + await init(); + }); + + describe('ProofMetadataResponse BigInt serialization', () => { + it('should serialize BigInt data to JSON as string', () => { + const chainId = new Uint8Array([1, 2, 3, 4]); + const metadata = new sdk.ResponseMetadata(1n, 2, 3, 4n, 5, chainId); + const proof = new sdk.ProofInfo( + new Uint8Array([1, 2, 3]), + new Uint8Array([4, 5, 6]), + new Uint8Array([7, 8, 9]), + 1, + new Uint8Array([10, 11, 12]), + 100, + ); + + // Create ProofMetadataResponse with BigInt data (simulating large credit balance) + const largeBigInt = 23522425453263151n; // Value > Number.MAX_SAFE_INTEGER + const response = new sdk.ProofMetadataResponse(largeBigInt, metadata, proof); + + // toJSON should not throw and should convert BigInt to string + const json = response.toJSON(); + expect(json).to.have.property('data'); + expect(json).to.have.property('metadata'); + expect(json).to.have.property('proof'); + + // BigInt should be serialized as string + expect(json.data).to.equal('23522425453263151'); + }); + + it('should serialize object with BigInt properties to JSON', () => { + const chainId = new Uint8Array([1, 2, 3, 4]); + const metadata = new sdk.ResponseMetadata(1n, 2, 3, 4n, 5, chainId); + const proof = new sdk.ProofInfo( + new Uint8Array([1, 2, 3]), + new Uint8Array([4, 5, 6]), + new Uint8Array([7, 8, 9]), + 1, + new Uint8Array([10, 11, 12]), + 100, + ); + + // Create ProofMetadataResponse with object containing BigInt + const dataWithBigInt = { + totalCredits: 23522425453263151n, + count: 42, + nested: { + balance: 9007199254740992n, // Exactly MAX_SAFE_INTEGER + 1 + }, + }; + const response = new sdk.ProofMetadataResponse(dataWithBigInt, metadata, proof); + + // toJSON should not throw + const json = response.toJSON(); + expect(json.data.totalCredits).to.equal('23522425453263151'); + expect(json.data.count).to.equal(42); + expect(json.data.nested.balance).to.equal('9007199254740992'); + }); + + it('should serialize array with BigInt values to JSON', () => { + const chainId = new Uint8Array([1, 2, 3, 4]); + const metadata = new sdk.ResponseMetadata(1n, 2, 3, 4n, 5, chainId); + const proof = new sdk.ProofInfo( + new Uint8Array([1, 2, 3]), + new Uint8Array([4, 5, 6]), + new Uint8Array([7, 8, 9]), + 1, + new Uint8Array([10, 11, 12]), + 100, + ); + + // Create ProofMetadataResponse with array containing BigInt + const dataWithBigIntArray = [1n, 2n, 23522425453263151n]; + const response = new sdk.ProofMetadataResponse(dataWithBigIntArray, metadata, proof); + + // toJSON should not throw + const json = response.toJSON(); + expect(json.data).to.deep.equal(['1', '2', '23522425453263151']); + }); + }); + + it('ResponseMetadata: getter returns Uint8Array, toJSON returns base64', () => { + const chainId = new Uint8Array([1, 2, 3, 4]); + const meta = new sdk.ResponseMetadata(1n, 2, 3, 4n, 5, chainId); + + const chainFromGetter = meta.chainId; + expect(chainFromGetter).to.be.instanceOf(Uint8Array); + expect([...chainFromGetter]).to.deep.equal([...chainId]); + + const json = meta.toJSON(); + expect(json.chainId).to.be.a('string'); + expect(json.chainId).to.equal(Buffer.from(chainId).toString('base64')); + + const metaFromJson = sdk.ResponseMetadata.fromJSON(json); + expect([...metaFromJson.chainId]).to.deep.equal([...chainId]); + }); + + describe('ProofMetadataResponse with Identity data', () => { + it('should serialize Identity as data with full JSON including metadata, proof, and identity', () => { + // Create metadata and proof + const chainId = new Uint8Array([1, 2, 3, 4]); + const metadata = new sdk.ResponseMetadata(100n, 50, 10, 1234567890n, 1, chainId); + const proof = new sdk.ProofInfo( + new Uint8Array([10, 20, 30]), // grovedbProof + new Uint8Array([40, 50, 60]), // quorumHash + new Uint8Array([70, 80, 90]), // signature + 5, // round + new Uint8Array([100, 110, 120]), // blockIdHash + 200, // quorumType + ); + + // Create an Identity + const identifierId = 'H2pb35GtKpjLinncBYeMsXkdDYXCbsFzzVmssce6pSJ1'; + const identity = new sdk.Identity(identifierId); + identity.balance = 1000000000n; // 10 DASH in credits + identity.revision = 5n; + + // Create ProofMetadataResponse with Identity.toJSON() as data + // Using toJSON() ensures all nested objects (like Identifier) + // are converted to JSON-safe values + const identityJson = identity.toJSON(); + const response = new sdk.ProofMetadataResponse(identityJson, metadata, proof); + + // Call toJSON - should produce full JSON representation + const json = response.toJSON(); + + // Verify structure + expect(json).to.have.property('data'); + expect(json).to.have.property('metadata'); + expect(json).to.have.property('proof'); + + // Verify metadata + expect(json.metadata.height).to.equal(100); + expect(json.metadata.coreChainLockedHeight).to.equal(50); + expect(json.metadata.epoch).to.equal(10); + expect(json.metadata.timeMs).to.equal(1234567890); + expect(json.metadata.protocolVersion).to.equal(1); + expect(json.metadata.chainId).to.equal(Buffer.from(chainId).toString('base64')); + + // Verify proof + expect(json.proof.grovedbProof).to.equal(Buffer.from([10, 20, 30]).toString('base64')); + expect(json.proof.quorumHash).to.equal(Buffer.from([40, 50, 60]).toString('base64')); + expect(json.proof.signature).to.equal(Buffer.from([70, 80, 90]).toString('base64')); + expect(json.proof.round).to.equal(5); + expect(json.proof.blockIdHash).to.equal(Buffer.from([100, 110, 120]).toString('base64')); + expect(json.proof.quorumType).to.equal(200); + + // Verify identity data - id should be Base58 in JSON + expect(json.data).to.have.property('id'); + expect(json.data.id).to.equal(identifierId); + // Values within safe integer range are numbers, large values would be strings + expect(json.data.balance).to.equal(1000000000); + expect(json.data.revision).to.equal(5); + expect(json.data.publicKeys).to.be.an('array'); + }); + + it('should round-trip ProofMetadataResponse with Identity data through JSON', () => { + const chainId = new Uint8Array([5, 6, 7, 8]); + const metadata = new sdk.ResponseMetadata(200n, 100, 20, 9876543210n, 2, chainId); + const proof = new sdk.ProofInfo( + new Uint8Array([1, 2, 3, 4, 5]), + new Uint8Array([6, 7, 8, 9, 10]), + new Uint8Array([11, 12, 13, 14, 15]), + 10, + new Uint8Array([16, 17, 18, 19, 20]), + 300, + ); + + const identifierId = 'H2pb35GtKpjLinncBYeMsXkdDYXCbsFzzVmssce6pSJ1'; + const identity = new sdk.Identity(identifierId); + identity.balance = 5000000000n; + identity.revision = 10n; + + // Use toJSON() to get JSON-safe representation of identity + const response = new sdk.ProofMetadataResponse(identity.toJSON(), metadata, proof); + const json = response.toJSON(); + + // Round-trip through JSON + const restored = sdk.ProofMetadataResponse.fromJSON(json); + const restoredJson = restored.toJSON(); + + // Verify JSON matches + expect(restoredJson).to.deep.equal(json); + + // Verify we can access the data + expect(restored.data.id).to.equal(identifierId); + // metadata.height getter returns BigInt + expect(restored.metadata.height).to.equal(200n); + expect(restored.proof.round).to.equal(10); + }); + }); + + it('Identifier-backed structs: toObject returns bytes, toJSON returns Base58', () => { + const bytes = new Uint8Array(32).fill(7); + const expectedBase58 = sdk.Identifier.fromBytes(Array.from(bytes)).toBase58(); + const identifier = sdk.Identifier.fromBase58(expectedBase58); + const identifier2 = sdk.Identifier.fromBase58(expectedBase58); + const info = new sdk.DpnsUsernameInfo('alice', identifier, identifier2); + + const identityBytes = info.identityId.toBytes(); + const documentBytes = info.documentId.toBytes(); + expect(identityBytes.length).to.equal(32); + expect(documentBytes.length).to.equal(32); + expect(Buffer.from(identityBytes)).to.deep.equal(Buffer.from(bytes)); + expect(Buffer.from(documentBytes)).to.deep.equal(Buffer.from(bytes)); + + const object = info.toObject(); + expect(Buffer.from(object.identityId)).to.deep.equal(Buffer.from(bytes)); + expect(Buffer.from(object.documentId)).to.deep.equal(Buffer.from(bytes)); + + const json = info.toJSON(); + expect(json.identityId).to.equal(expectedBase58); + expect(json.documentId).to.equal(expectedBase58); + + const roundtrip = sdk.DpnsUsernameInfo.fromJSON(json); + expect(roundtrip.identityId.toBase58()).to.equal(expectedBase58); + expect(roundtrip.documentId.toBase58()).to.equal(expectedBase58); + }); +}); diff --git a/packages/wasm-sdk/tests/unit/data-contract.spec.mjs b/packages/wasm-sdk/tests/unit/data-contract.spec.mjs index 98421b9ff19..dd77b13f45b 100644 --- a/packages/wasm-sdk/tests/unit/data-contract.spec.mjs +++ b/packages/wasm-sdk/tests/unit/data-contract.spec.mjs @@ -21,9 +21,9 @@ const testContractAcrossVersions = ( ) => { compatibleVersions.forEach((version) => { it(`should work with platform version ${version}`, () => { - const contract = sdk.DataContract.fromValue(contractFixture, true, version); + const contract = sdk.DataContract.fromJSON(contractFixture, true, version); expect(contract).to.be.ok(); - expect(contract.id.base58()).to.equal(contractFixture.id); + expect(contract.id.toBase58()).to.equal(contractFixture.id); const roundTripped = contract.toJSON(version); expect(roundTripped.id).to.equal(contractFixture.id); @@ -35,7 +35,7 @@ const testContractAcrossVersions = ( incompatibleVersions.forEach((version) => { it(`should fail with platform version ${version}`, () => { expect(() => { - sdk.DataContract.fromValue(contractFixture, true, version); + sdk.DataContract.fromJSON(contractFixture, true, version); }).to.throw(/unknown version|dpp unknown version/); }); }); @@ -48,14 +48,14 @@ describe('DataContract', () => { describe('Contract Creation', () => { it('should create a V0 contract from JSON and expose all properties', async () => { - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractFixtureV0, true, PLATFORM_VERSION_CONTRACT_V0, ); expect(contract).to.be.ok(); - expect(contract.id.base58()).to.equal(contractFixtureV0.id); + expect(contract.id.toBase58()).to.equal(contractFixtureV0.id); const roundTripped = contract.toJSON(PLATFORM_VERSION_CONTRACT_V0); expect(roundTripped).to.be.an('object'); @@ -78,14 +78,14 @@ describe('DataContract', () => { // TODO: enable test once an SDK fix to support this is merged it.skip('should create a V1 contract from JSON and expose all properties including tokens and groups', async () => { - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractFixtureV1, true, PLATFORM_VERSION_CONTRACT_V1, ); expect(contract).to.be.ok(); - expect(contract.id.base58()).to.equal(contractFixtureV1.id); + expect(contract.id.toBase58()).to.equal(contractFixtureV1.id); const roundTripped = contract.toJSON(PLATFORM_VERSION_CONTRACT_V1); expect(roundTripped).to.be.an('object'); @@ -113,7 +113,7 @@ describe('DataContract', () => { it('should create a contract with only document schemas (no tokens)', () => { // V0 fixture already has only documents, no tokens - verify it works - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractFixtureV0, true, PLATFORM_VERSION_CONTRACT_V0, @@ -133,7 +133,7 @@ describe('DataContract', () => { documentSchemas: {}, }; - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractWithOnlyTokens, true, PLATFORM_VERSION_CONTRACT_V1, @@ -149,7 +149,7 @@ describe('DataContract', () => { describe('Version Compatibility', () => { it('should fail to create a V1 contract with V0 platform version', async () => { expect(() => { - sdk.DataContract.fromValue( + sdk.DataContract.fromJSON( contractFixtureV1, true, PLATFORM_VERSION_CONTRACT_V0, @@ -161,15 +161,15 @@ describe('DataContract', () => { describe('Validation', () => { it('should handle invalid JSON input gracefully', () => { expect(() => { - sdk.DataContract.fromValue(null, true, PLATFORM_VERSION_CONTRACT_V0); + sdk.DataContract.fromJSON(null, true, PLATFORM_VERSION_CONTRACT_V0); }).to.throw(); expect(() => { - sdk.DataContract.fromValue({}, true, PLATFORM_VERSION_CONTRACT_V0); + sdk.DataContract.fromJSON({}, true, PLATFORM_VERSION_CONTRACT_V0); }).to.throw(); expect(() => { - sdk.DataContract.fromValue({ id: 'invalid' }, true, PLATFORM_VERSION_CONTRACT_V0); + sdk.DataContract.fromJSON({ id: 'invalid' }, true, PLATFORM_VERSION_CONTRACT_V0); }).to.throw(); }); @@ -210,7 +210,7 @@ describe('DataContract', () => { }; expect(() => { - sdk.DataContract.fromValue( + sdk.DataContract.fromJSON( contractWithEmptySchemas, true, PLATFORM_VERSION_CONTRACT_V0, @@ -221,7 +221,7 @@ describe('DataContract', () => { describe('Data Preservation', () => { it('should preserve all data through JSON round-trip for V0 contract', async () => { - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractFixtureV0, true, PLATFORM_VERSION_CONTRACT_V0, @@ -229,7 +229,7 @@ describe('DataContract', () => { const roundTripped = contract.toJSON(PLATFORM_VERSION_CONTRACT_V0); // Create a new contract from the round-tripped JSON - const contract2 = sdk.DataContract.fromValue( + const contract2 = sdk.DataContract.fromJSON( roundTripped, true, PLATFORM_VERSION_CONTRACT_V0, @@ -243,7 +243,7 @@ describe('DataContract', () => { }); it('should preserve all data through JSON round-trip for V1 contract', async () => { - const contract = sdk.DataContract.fromValue( + const contract = sdk.DataContract.fromJSON( contractFixtureV1, true, PLATFORM_VERSION_CONTRACT_V1, @@ -251,7 +251,7 @@ describe('DataContract', () => { const roundTripped = contract.toJSON(PLATFORM_VERSION_CONTRACT_V1); // Create a new contract from the round-tripped JSON - const contract2 = sdk.DataContract.fromValue( + const contract2 = sdk.DataContract.fromJSON( roundTripped, true, PLATFORM_VERSION_CONTRACT_V1, @@ -263,23 +263,43 @@ describe('DataContract', () => { contract.free(); contract2.free(); }); + + it('should preserve all data through JSON round-trip for V1 contract (object output)', async () => { + const contract = sdk.DataContract.fromJSON( + contractFixtureV1, + true, + PLATFORM_VERSION_CONTRACT_V1, + ); + const jsonRepresentation = contract.toJSON(PLATFORM_VERSION_CONTRACT_V1); + + const contract2 = sdk.DataContract.fromJSON( + jsonRepresentation, + true, + PLATFORM_VERSION_CONTRACT_V1, + ); + + expect(contract2.toJSON(PLATFORM_VERSION_CONTRACT_V1)).to.deep.equal(jsonRepresentation); + + contract.free(); + contract2.free(); + }); }); describe('Memory Management', () => { it('should handle memory management properly with multiple contracts', async () => { - const contract1 = sdk.DataContract.fromValue( + const contract1 = sdk.DataContract.fromJSON( contractFixtureV0, true, PLATFORM_VERSION_CONTRACT_V0, ); - const contract2 = sdk.DataContract.fromValue( + const contract2 = sdk.DataContract.fromJSON( contractFixtureV1, true, PLATFORM_VERSION_CONTRACT_V1, ); - expect(contract1.id.base58()).to.equal(contractFixtureV0.id); - expect(contract2.id.base58()).to.equal(contractFixtureV1.id); + expect(contract1.id.toBase58()).to.equal(contractFixtureV0.id); + expect(contract2.id.toBase58()).to.equal(contractFixtureV1.id); contract1.free(); contract2.free(); @@ -306,25 +326,25 @@ describe('DataContract', () => { invalidVersions.forEach((version) => { expect(() => { - sdk.DataContract.fromValue(contractFixtureV0, true, version); + sdk.DataContract.fromJSON(contractFixtureV0, true, version); }).to.throw(/unknown version|unknown platform version value/); }); }); it('should handle version boundary correctly at V9 transition', () => { // V0 contract should work in V9 (backward compatibility) - const contract = sdk.DataContract.fromValue(contractFixtureV0, true, 9); - expect(contract.id.base58()).to.equal(contractFixtureV0.id); + const contract = sdk.DataContract.fromJSON(contractFixtureV0, true, 9); + expect(contract.id.toBase58()).to.equal(contractFixtureV0.id); contract.free(); // V1 contract should work in V9 (first supported version) - const contractV1 = sdk.DataContract.fromValue(contractFixtureV1, true, 9); - expect(contractV1.id.base58()).to.equal(contractFixtureV1.id); + const contractV1 = sdk.DataContract.fromJSON(contractFixtureV1, true, 9); + expect(contractV1.id.toBase58()).to.equal(contractFixtureV1.id); contractV1.free(); // V1 contract should fail in V8 (last unsupported version) expect(() => { - sdk.DataContract.fromValue(contractFixtureV1, true, 8); + sdk.DataContract.fromJSON(contractFixtureV1, true, 8); }).to.throw(/dpp unknown version/); }); }); diff --git a/yarn.lock b/yarn.lock index df9b9322c70..e91698f6a55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2005,6 +2005,7 @@ __metadata: resolution: "@dashevo/wasm-dpp2@workspace:packages/wasm-dpp2" dependencies: assert: "npm:^2.0.0" + bs58: "npm:^4.0.1" buffer: "npm:^6.0.3" chai: "npm:^4.3.10" chai-as-promised: "npm:^7.1.1" @@ -3366,11 +3367,12 @@ __metadata: linkType: hard "@types/bs58@npm:^4.0.1": - version: 4.0.1 - resolution: "@types/bs58@npm:4.0.1" + version: 4.0.4 + resolution: "@types/bs58@npm:4.0.4" dependencies: + "@types/node": "npm:*" base-x: "npm:^3.0.6" - checksum: 10/5063fed6bb3816f28eef3e689a05f8f85d03ce7c2e3ceefe77c349ed147023999eb024e863bf3fa81ba2bded690e5296efe4d34864c875097bd261614e37f786 + checksum: 10/9cac5a00343756f887ad906c10c71464620d352af40ff61373dcede434880a769be8b78380b890e9b13506851af4611ab59e7d4b7159af04f445e1fd5c34e3d0 languageName: node linkType: hard