diff --git a/.changeset/witty-bananas-yell.md b/.changeset/witty-bananas-yell.md new file mode 100644 index 0000000000000..81f0ec7a0b57c --- /dev/null +++ b/.changeset/witty-bananas-yell.md @@ -0,0 +1,5 @@ +--- +"@mysten/sui.js": minor +--- + +Use DynamicFieldName struct instead of string for dynamic field's name diff --git a/Cargo.lock b/Cargo.lock index e7c1bf6880513..dd59ee756797f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7652,9 +7652,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde 1.0.152", ] diff --git a/crates/sui-core/src/authority.rs b/crates/sui-core/src/authority.rs index 1e8170597d263..7d96717e1efe2 100644 --- a/crates/sui-core/src/authority.rs +++ b/crates/sui-core/src/authority.rs @@ -46,7 +46,7 @@ use narwhal_config::{ use sui_adapter::{adapter, execution_mode}; use sui_config::genesis::Genesis; use sui_json_rpc_types::{ - type_and_fields_from_move_struct, DevInspectResults, SuiEvent, SuiEventEnvelope, + type_and_fields_from_move_struct, DevInspectResults, SuiEvent, SuiEventEnvelope, SuiMoveValue, SuiTransactionEffects, }; use sui_macros::nondeterministic; @@ -60,7 +60,7 @@ use sui_storage::{ }; use sui_types::committee::{EpochId, ProtocolVersion}; use sui_types::crypto::{sha3_hash, AuthorityKeyPair, NetworkKeyPair, Signer}; -use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldType}; +use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldName, DynamicFieldType}; use sui_types::event::{Event, EventID}; use sui_types::gas::{GasCostSummary, GasPrice, SuiCostTable, SuiGasStatus}; use sui_types::messages_checkpoint::{ @@ -1273,9 +1273,16 @@ impl AuthorityState { self.module_cache.as_ref(), )?; - let (name, type_, object_id) = + let (name_value, type_, object_id) = DynamicFieldInfo::parse_move_object(&move_struct).tap_err(|e| warn!("{e}"))?; + let name_type = DynamicFieldInfo::try_extract_field_name(&move_object.type_, &type_)?; + + let name = DynamicFieldName { + type_: name_type, + value: SuiMoveValue::from(name_value).to_json_value(), + }; + Ok(Some(match type_ { DynamicFieldType::DynamicObject => { // Find the actual object from storage using the object id obtained from the wrapper. @@ -2019,7 +2026,7 @@ impl AuthorityState { pub fn get_dynamic_field_object_id( &self, owner: ObjectID, - name: &str, + name: &DynamicFieldName, ) -> SuiResult> { if let Some(indexes) = &self.indexes { indexes.get_dynamic_field_object_id(owner, name) diff --git a/crates/sui-core/src/event_handler.rs b/crates/sui-core/src/event_handler.rs index 3e5db4d27f345..d22d955ced227 100644 --- a/crates/sui-core/src/event_handler.rs +++ b/crates/sui-core/src/event_handler.rs @@ -128,11 +128,7 @@ impl EventHandler { Event::move_event_to_move_struct(type_, contents, self.module_cache.as_ref())?; // Convert into `SuiMoveStruct` which is a mirror of MoveStruct but with additional type supports, (e.g. ascii::String). let sui_move_struct = SuiMoveStruct::from(move_struct); - Some(sui_move_struct.to_json_value().map_err(|e| { - SuiError::ObjectSerializationError { - error: e.to_string(), - } - })?) + Some(sui_move_struct.to_json_value()) } _ => None, }; diff --git a/crates/sui-core/src/unit_tests/authority_tests.rs b/crates/sui-core/src/unit_tests/authority_tests.rs index 0884faab56aa6..97508a80741e0 100644 --- a/crates/sui-core/src/unit_tests/authority_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_tests.rs @@ -17,6 +17,7 @@ use move_binary_format::{ file_format::{self, AddressIdentifierIndex, IdentifierIndex, ModuleHandle}, CompiledModule, }; +use move_core_types::identifier::IdentStr; use move_core_types::{ account_address::AccountAddress, ident_str, identifier::Identifier, language_storage::TypeTag, }; @@ -25,6 +26,7 @@ use rand::{ prelude::StdRng, Rng, SeedableRng, }; +use serde_json::json; use std::collections::HashSet; use std::fs; use std::future::Future; @@ -38,6 +40,7 @@ use sui_types::utils::{ use sui_types::{SUI_CLOCK_OBJECT_ID, SUI_CLOCK_OBJECT_SHARED_VERSION, SUI_FRAMEWORK_OBJECT_ID}; use crate::epoch::epoch_metrics::EpochMetrics; +use move_core_types::parser::parse_type_tag; use std::{convert::TryInto, env}; use sui_macros::sim_test; use sui_protocol_config::{ProtocolConfig, SupportedProtocolVersions}; @@ -3183,6 +3186,22 @@ async fn test_store_revert_unwrap_move_call() { } #[tokio::test] async fn test_store_get_dynamic_object() { + let (_, fields) = create_and_retrieve_df_info(ident_str!("add_ofield")).await; + assert_eq!(fields.len(), 1); + assert_eq!(fields[0].type_, DynamicFieldType::DynamicObject); +} + +#[tokio::test] +async fn test_store_get_dynamic_field() { + let (_, fields) = create_and_retrieve_df_info(ident_str!("add_field")).await; + + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicField)); + assert_eq!(json!(true), fields[0].name.value); + assert_eq!(TypeTag::Bool, fields[0].name.type_) +} + +async fn create_and_retrieve_df_info(function: &IdentStr) -> (SuiAddress, Vec) { let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); let gas_object_id = ObjectID::random(); let (authority_state, object_basics) = @@ -3222,7 +3241,7 @@ async fn test_store_get_dynamic_object() { sender, object_basics.0, ident_str!("object_basics").to_owned(), - ident_str!("add_ofield").to_owned(), + function.to_owned(), vec![], create_inner_effects.gas_object.0, vec![ @@ -3245,82 +3264,82 @@ async fn test_store_get_dynamic_object() { assert!(add_effects.status.is_ok()); assert_eq!(add_effects.created.len(), 1); - let fields = authority_state - .get_dynamic_fields(outer_v0.0, None, usize::MAX) - .unwrap(); - assert_eq!(fields.len(), 1); - assert_eq!(fields[0].type_, DynamicFieldType::DynamicObject); + ( + sender, + authority_state + .get_dynamic_fields(outer_v0.0, None, usize::MAX) + .unwrap(), + ) } #[tokio::test] -async fn test_store_get_dynamic_field() { - let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); - let gas_object_id = ObjectID::random(); - let (authority_state, object_basics) = - init_state_with_ids_and_object_basics(vec![(sender, gas_object_id)]).await; +async fn test_dynamic_field_struct_name_parsing() { + let (_, fields) = create_and_retrieve_df_info(ident_str!("add_field_with_struct_name")).await; - let create_outer_effects = create_move_object( - &object_basics.0, - &authority_state, - &gas_object_id, - &sender, - &sender_key, + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicField)); + assert_eq!(json!({"name_str": "Test Name"}), fields[0].name.value); + assert_eq!( + parse_type_tag("0x0::object_basics::Name").unwrap(), + fields[0].name.type_ ) - .await - .unwrap(); +} - assert!(create_outer_effects.status.is_ok()); - assert_eq!(create_outer_effects.created.len(), 1); +#[tokio::test] +async fn test_dynamic_field_bytearray_name_parsing() { + let (_, fields) = + create_and_retrieve_df_info(ident_str!("add_field_with_bytearray_name")).await; - let create_inner_effects = create_move_object( - &object_basics.0, - &authority_state, - &gas_object_id, - &sender, - &sender_key, - ) - .await - .unwrap(); + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicField)); + assert_eq!(parse_type_tag("vector").unwrap(), fields[0].name.type_); + assert_eq!(json!("Test Name".as_bytes()), fields[0].name.value); +} - assert!(create_inner_effects.status.is_ok()); - assert_eq!(create_inner_effects.created.len(), 1); +#[tokio::test] +async fn test_dynamic_field_address_name_parsing() { + let (sender, fields) = + create_and_retrieve_df_info(ident_str!("add_field_with_address_name")).await; - let outer_v0 = create_outer_effects.created[0].0; - let inner_v0 = create_inner_effects.created[0].0; + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicField)); + assert_eq!(parse_type_tag("address").unwrap(), fields[0].name.type_); + assert_eq!(json!(sender), fields[0].name.value); +} - let add_txn = to_sender_signed_transaction( - TransactionData::new_move_call_with_dummy_gas_price( - sender, - object_basics.0, - ident_str!("object_basics").to_owned(), - ident_str!("add_field").to_owned(), - vec![], - create_inner_effects.gas_object.0, - vec![ - CallArg::Object(ObjectArg::ImmOrOwnedObject(outer_v0)), - CallArg::Object(ObjectArg::ImmOrOwnedObject(inner_v0)), - ], - MAX_GAS, - ), - &sender_key, - ); +#[tokio::test] +async fn test_dynamic_object_field_struct_name_parsing() { + let (_, fields) = create_and_retrieve_df_info(ident_str!("add_ofield_with_struct_name")).await; - let add_cert = init_certified_transaction(add_txn, &authority_state); + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicObject)); + assert_eq!(json!({"name_str": "Test Name"}), fields[0].name.value); + assert_eq!( + parse_type_tag("0x0::object_basics::Name").unwrap(), + fields[0].name.type_ + ) +} - let add_effects = authority_state - .try_execute_for_test(&add_cert) - .await - .unwrap() - .into_message(); +#[tokio::test] +async fn test_dynamic_object_field_bytearray_name_parsing() { + let (_, fields) = + create_and_retrieve_df_info(ident_str!("add_ofield_with_bytearray_name")).await; - assert!(add_effects.status.is_ok()); - assert_eq!(add_effects.created.len(), 1); + assert_eq!(fields.len(), 1); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicObject)); + assert_eq!(parse_type_tag("vector").unwrap(), fields[0].name.type_); + assert_eq!(json!("Test Name".as_bytes()), fields[0].name.value); +} + +#[tokio::test] +async fn test_dynamic_object_field_address_name_parsing() { + let (sender, fields) = + create_and_retrieve_df_info(ident_str!("add_ofield_with_address_name")).await; - let fields = authority_state - .get_dynamic_fields(outer_v0.0, None, usize::MAX) - .unwrap(); assert_eq!(fields.len(), 1); - assert!(matches!(fields[0].type_, DynamicFieldType::DynamicField)); + assert!(matches!(fields[0].type_, DynamicFieldType::DynamicObject)); + assert_eq!(parse_type_tag("address").unwrap(), fields[0].name.type_); + assert_eq!(json!(sender), fields[0].name.value); } #[tokio::test] diff --git a/crates/sui-core/src/unit_tests/data/object_basics/sources/object_basics.move b/crates/sui-core/src/unit_tests/data/object_basics/sources/object_basics.move index 9b485f7dd8741..e074e161447cf 100644 --- a/crates/sui-core/src/unit_tests/data/object_basics/sources/object_basics.move +++ b/crates/sui-core/src/unit_tests/data/object_basics/sources/object_basics.move @@ -107,6 +107,34 @@ module examples::object_basics { ); } + struct Name has copy, drop, store { + name_str: std::string::String + } + + public entry fun add_field_with_struct_name(o: &mut Object, v: Object) { + sui::dynamic_field::add(&mut o.id, Name {name_str: std::string::utf8(b"Test Name")}, v); + } + + public entry fun add_ofield_with_struct_name(o: &mut Object, v: Object) { + ofield::add(&mut o.id, Name {name_str: std::string::utf8(b"Test Name")}, v); + } + + public entry fun add_field_with_bytearray_name(o: &mut Object, v: Object) { + sui::dynamic_field::add(&mut o.id,b"Test Name", v); + } + + public entry fun add_ofield_with_bytearray_name(o: &mut Object, v: Object) { + ofield::add(&mut o.id,b"Test Name", v); + } + + public entry fun add_field_with_address_name(o: &mut Object, v: Object, ctx: &mut TxContext) { + sui::dynamic_field::add(&mut o.id,tx_context::sender(ctx), v); + } + + public entry fun add_ofield_with_address_name(o: &mut Object, v: Object, ctx: &mut TxContext) { + ofield::add(&mut o.id,tx_context::sender(ctx), v); + } + public entry fun generic_test() {} public entry fun use_clock(_clock: &Clock) {} diff --git a/crates/sui-core/src/unit_tests/event_handler_tests.rs b/crates/sui-core/src/unit_tests/event_handler_tests.rs index 2747a2fbd6473..0632b75e9eef7 100644 --- a/crates/sui-core/src/unit_tests/event_handler_tests.rs +++ b/crates/sui-core/src/unit_tests/event_handler_tests.rs @@ -37,7 +37,7 @@ fn test_to_json_value() { MoveStruct::simple_deserialize(&event_bytes, &TestEvent::layout()) .unwrap() .into(); - let json_value = sui_move_struct.to_json_value().unwrap(); + let json_value = sui_move_struct.to_json_value(); assert_eq!( Some(&json!("1000000")), json_value.pointer("/coins/0/balance") diff --git a/crates/sui-json-rpc-types/src/lib.rs b/crates/sui-json-rpc-types/src/lib.rs index 6274a6c0be5c1..597120113e1cf 100644 --- a/crates/sui-json-rpc-types/src/lib.rs +++ b/crates/sui-json-rpc-types/src/lib.rs @@ -26,7 +26,7 @@ use schemars::JsonSchema; use serde::ser::Error; use serde::Deserialize; use serde::Serialize; -use serde_json::Value; +use serde_json::{json, Value}; use serde_with::serde_as; use sui_json::SuiJsonValue; use sui_protocol_config::ProtocolConfig; @@ -1185,6 +1185,22 @@ pub enum SuiMoveValue { Option(Box>), } +impl SuiMoveValue { + /// Extract values from MoveValue without type information in json format + pub fn to_json_value(self) -> Value { + match self { + SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(), + SuiMoveValue::Vector(values) => SuiMoveStruct::Runtime(values).to_json_value(), + SuiMoveValue::Number(v) => json!(v), + SuiMoveValue::Bool(v) => json!(v), + SuiMoveValue::Address(v) => json!(v), + SuiMoveValue::String(v) => json!(v), + SuiMoveValue::UID { id } => json!({ "id": id }), + SuiMoveValue::Option(v) => json!(v), + } + } +} + impl Display for SuiMoveValue { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut writer = String::new(); @@ -1268,41 +1284,26 @@ pub enum SuiMoveStruct { } impl SuiMoveStruct { - pub fn to_json_value(self) -> Result { + /// Extract values from MoveStruct without type information in json format + pub fn to_json_value(self) -> Value { // Unwrap MoveStructs - let unwrapped = match self { + match self { SuiMoveStruct::Runtime(values) => { let values = values .into_iter() - .map(|value| match value { - SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(), - SuiMoveValue::Vector(values) => { - SuiMoveStruct::Runtime(values).to_json_value() - } - _ => serde_json::to_value(&value), - }) - .collect::, _>>()?; - serde_json::to_value(&values) + .map(|value| value.to_json_value()) + .collect::>(); + json!(values) } // We only care about values here, assuming struct type information is known at the client side. SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => { let fields = fields .into_iter() - .map(|(key, value)| { - let value = match value { - SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(), - SuiMoveValue::Vector(values) => { - SuiMoveStruct::Runtime(values).to_json_value() - } - _ => serde_json::to_value(&value), - }; - value.map(|value| (key, value)) - }) - .collect::, _>>()?; - serde_json::to_value(&fields) + .map(|(key, value)| (key, value.to_json_value())) + .collect::>(); + json!(fields) } - }?; - serde_json::to_value(&unwrapped) + } } } diff --git a/crates/sui-json-rpc/src/api.rs b/crates/sui-json-rpc/src/api.rs index 6d8f319294ff8..7369a2b57238f 100644 --- a/crates/sui-json-rpc/src/api.rs +++ b/crates/sui-json-rpc/src/api.rs @@ -23,6 +23,7 @@ use sui_types::base_types::{ ObjectID, SequenceNumber, SuiAddress, TransactionDigest, TxSequenceNumber, }; use sui_types::committee::EpochId; +use sui_types::dynamic_field::DynamicFieldName; use sui_types::event::EventID; use sui_types::governance::DelegatedStake; use sui_types::messages::CommitteeInfoResponse; @@ -164,7 +165,7 @@ pub trait RpcReadApi { /// The ID of the queried parent object parent_object_id: ObjectID, /// The Name of the dynamic field - name: String, + name: DynamicFieldName, ) -> RpcResult; } diff --git a/crates/sui-json-rpc/src/read_api.rs b/crates/sui-json-rpc/src/read_api.rs index 45d24aa410057..558d352b331b0 100644 --- a/crates/sui-json-rpc/src/read_api.rs +++ b/crates/sui-json-rpc/src/read_api.rs @@ -34,6 +34,7 @@ use sui_types::move_package::normalize_modules; use sui_types::object::{Data, ObjectRead}; use sui_types::query::TransactionQuery; +use sui_types::dynamic_field::DynamicFieldName; use tracing::debug; use crate::api::RpcFullNodeReadApiServer; @@ -132,14 +133,14 @@ impl RpcReadApiServer for ReadApi { async fn get_dynamic_field_object( &self, parent_object_id: ObjectID, - name: String, + name: DynamicFieldName, ) -> RpcResult { let id = self .state .get_dynamic_field_object_id(parent_object_id, &name) .map_err(|e| anyhow!("{e}"))? .ok_or_else(|| { - anyhow!("Cannot find dynamic field [{name}] for object [{parent_object_id}].") + anyhow!("Cannot find dynamic field [{name:?}] for object [{parent_object_id}].") })?; self.get_object(id).await } diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index d03b4710782fe..23195ea89bcdf 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -844,7 +844,7 @@ "description": "The Name of the dynamic field", "required": true, "schema": { - "type": "string" + "$ref": "#/components/schemas/DynamicFieldName" } } ], @@ -3475,7 +3475,7 @@ "$ref": "#/components/schemas/ObjectDigest" }, "name": { - "type": "string" + "$ref": "#/components/schemas/DynamicFieldName" }, "objectId": { "$ref": "#/components/schemas/ObjectID" @@ -3491,6 +3491,19 @@ } } }, + "DynamicFieldName": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string" + }, + "value": true + } + }, "DynamicFieldType": { "type": "string", "enum": [ diff --git a/crates/sui-storage/src/indexes.rs b/crates/sui-storage/src/indexes.rs index 05caeb6d5092b..a79e7f6386261 100644 --- a/crates/sui-storage/src/indexes.rs +++ b/crates/sui-storage/src/indexes.rs @@ -19,7 +19,7 @@ use typed_store_derive::DBMapUtils; use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest, TxSequenceNumber}; use sui_types::base_types::{ObjectInfo, ObjectRef}; -use sui_types::dynamic_field::DynamicFieldInfo; +use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldName}; use sui_types::error::{SuiError, SuiResult}; use sui_types::fp_ensure; use sui_types::object::Owner; @@ -554,7 +554,7 @@ impl IndexStore { pub fn get_dynamic_field_object_id( &self, object: ObjectID, - name: &str, + name: &DynamicFieldName, ) -> SuiResult> { debug!(?object, "get_dynamic_field_object_id"); Ok(self @@ -563,7 +563,11 @@ impl IndexStore { .iter() // The object id 0 is the smallest possible .skip_to(&(object, ObjectID::ZERO))? - .find(|((object_owner, _), info)| (object_owner == &object && info.name == name)) + .find(|((object_owner, _), info)| { + object_owner == &object + && info.name.type_ == name.type_ + && info.name.value == name.value + }) .map(|(_, object_info)| object_info.object_id)) } diff --git a/crates/sui-types/Cargo.toml b/crates/sui-types/Cargo.toml index da58d71206141..08033e84a12fa 100644 --- a/crates/sui-types/Cargo.toml +++ b/crates/sui-types/Cargo.toml @@ -18,7 +18,7 @@ serde = { version = "1.0.144", features = ["derive"] } serde-name = "0.2.1" thiserror = "1.0.34" tracing = "0.1" -serde_bytes = "0.11.7" +serde_bytes = "0.11.9" serde_json = "1.0.88" serde_with = "2.1.0" serde_repr = "0.1" diff --git a/crates/sui-types/src/base_types.rs b/crates/sui-types/src/base_types.rs index 92809d1b54ccf..c520c0cf5ef91 100644 --- a/crates/sui-types/src/base_types.rs +++ b/crates/sui-types/src/base_types.rs @@ -2,21 +2,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use anyhow::anyhow; -use fastcrypto::encoding::decode_bytes_hex; -use move_core_types::account_address::AccountAddress; -use move_core_types::ident_str; -use move_core_types::identifier::IdentStr; -use move_core_types::language_storage::StructTag; -use rand::Rng; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use std::cmp::max; -use std::convert::{TryFrom, TryInto}; -use std::fmt; -use std::str::FromStr; - pub use crate::committee::EpochId; use crate::crypto::{ AuthorityPublicKey, AuthorityPublicKeyBytes, KeypairTraits, PublicKey, SignatureScheme, @@ -31,9 +16,24 @@ use crate::gas_coin::GasCoin; use crate::multisig::MultiSigPublicKey; use crate::object::{Object, Owner}; use crate::signature::GenericSignature; +use crate::sui_serde::HexAccountAddress; use crate::sui_serde::Readable; +use anyhow::anyhow; +use fastcrypto::encoding::decode_bytes_hex; use fastcrypto::encoding::{Encoding, Hex}; use fastcrypto::hash::{HashFunction, Sha3_256}; +use move_core_types::account_address::AccountAddress; +use move_core_types::ident_str; +use move_core_types::identifier::IdentStr; +use move_core_types::language_storage::StructTag; +use rand::Rng; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::cmp::max; +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::str::FromStr; #[cfg(test)] #[path = "unit_tests/base_types_tests.rs"] @@ -74,7 +74,7 @@ pub type AuthorityName = AuthorityPublicKeyBytes; #[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)] pub struct ObjectID( #[schemars(with = "Hex")] - #[serde_as(as = "Readable")] + #[serde_as(as = "Readable")] AccountAddress, ); diff --git a/crates/sui-types/src/digests.rs b/crates/sui-types/src/digests.rs index 383add21f888a..9da9291dd523f 100644 --- a/crates/sui-types/src/digests.rs +++ b/crates/sui-types/src/digests.rs @@ -8,7 +8,6 @@ use fastcrypto::encoding::{Base58, Base64, Encoding}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, Bytes}; - /// A representation of a SHA3-256 Digest #[serde_as] #[derive( diff --git a/crates/sui-types/src/dynamic_field.rs b/crates/sui-types/src/dynamic_field.rs index 92e7bf4f59163..e62de570e01bc 100644 --- a/crates/sui-types/src/dynamic_field.rs +++ b/crates/sui-types/src/dynamic_field.rs @@ -1,20 +1,23 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::base_types::ObjectDigest; +use crate::error::{SuiError, SuiResult}; +use crate::sui_serde::Readable; +use crate::{ObjectID, SequenceNumber, SUI_FRAMEWORK_ADDRESS}; use move_core_types::language_storage::{StructTag, TypeTag}; use move_core_types::value::{MoveStruct, MoveValue}; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; - -use crate::base_types::ObjectDigest; -use crate::error::{SuiError, SuiResult}; -use crate::{ObjectID, SequenceNumber, SUI_FRAMEWORK_ADDRESS}; - -#[derive(Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug)] +use serde_json::Value; +use serde_with::serde_as; +use serde_with::DisplayFromStr; +use std::fmt::{Display, Formatter}; +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] #[serde(rename_all = "camelCase")] pub struct DynamicFieldInfo { - pub name: String, + pub name: DynamicFieldName, pub type_: DynamicFieldType, pub object_type: String, pub object_id: ObjectID, @@ -22,6 +25,25 @@ pub struct DynamicFieldInfo { pub digest: ObjectDigest, } +#[serde_as] +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(rename_all = "camelCase")] +pub struct DynamicFieldName { + #[schemars(with = "String")] + #[serde_as(as = "Readable")] + pub type_: TypeTag, + // Bincode does not like serde_json::Value, rocksdb will not insert the value without serializing value as string. + #[schemars(with = "Value")] + #[serde_as(as = "Readable<_, DisplayFromStr>")] + pub value: Value, +} + +impl Display for DynamicFieldName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.type_, self.value) + } +} + #[derive(Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug)] pub enum DynamicFieldType { #[serde(rename_all = "camelCase")] @@ -36,9 +58,25 @@ impl DynamicFieldInfo { && tag.name.as_str() == "Field" } + pub fn try_extract_field_name(tag: &StructTag, type_: &DynamicFieldType) -> SuiResult { + match (type_, tag.type_params.first()) { + (DynamicFieldType::DynamicField, Some(name_type)) => Ok(name_type.clone()), + (DynamicFieldType::DynamicObject, Some(TypeTag::Struct(s))) => Ok(s + .type_params + .first() + .ok_or_else(|| SuiError::ObjectDeserializationError { + error: format!("Error extracting dynamic object name from object: {tag}"), + })? + .clone()), + _ => Err(SuiError::ObjectDeserializationError { + error: format!("Error extracting dynamic object name from object: {tag}"), + }), + } + } + pub fn parse_move_object( move_struct: &MoveStruct, - ) -> SuiResult<(String, DynamicFieldType, ObjectID)> { + ) -> SuiResult<(MoveValue, DynamicFieldType, ObjectID)> { let name = extract_field_from_move_struct(move_struct, "name").ok_or_else(|| { SuiError::ObjectDeserializationError { error: "Cannot extract [name] field from sui::dynamic_field::Field".to_string(), @@ -70,7 +108,7 @@ impl DynamicFieldInfo { sui::dynamic_field::Field, {value:?}" ), })?; - (name.to_string(), DynamicFieldType::DynamicObject, object_id) + (name.clone(), DynamicFieldType::DynamicObject, object_id) } else { // ID of the Field object let object_id = extract_object_id(move_struct).ok_or_else(|| { @@ -81,7 +119,7 @@ impl DynamicFieldInfo { ), } })?; - (name.to_string(), DynamicFieldType::DynamicField, object_id) + (name.clone(), DynamicFieldType::DynamicField, object_id) }) } } diff --git a/crates/sui-types/src/sui_serde.rs b/crates/sui-types/src/sui_serde.rs index dfd64d35f1c6e..b648b65058340 100644 --- a/crates/sui-types/src/sui_serde.rs +++ b/crates/sui-types/src/sui_serde.rs @@ -4,8 +4,7 @@ use std::fmt::Debug; use std::marker::PhantomData; -use anyhow::anyhow; -use fastcrypto::encoding::Encoding; +use fastcrypto::encoding::Hex; use move_core_types::account_address::AccountAddress; use serde; use serde::de::{Deserializer, Error}; @@ -31,108 +30,83 @@ where S::Error::custom(format!("byte serialization failed, cause by: {:?}", e)) } -/// Use with serde_as to encode/decode bytes to/from Base64/Hex for human-readable serializer and deserializer -/// E : Encoding of the human readable output -/// R : serde_as SerializeAs/DeserializeAs delegation +/// Use with serde_as to control serde for human-readable serialization and deserialization +/// `H` : serde_as SerializeAs/DeserializeAs delegation for human readable in/output +/// `R` : serde_as SerializeAs/DeserializeAs delegation for non-human readable in/output /// /// # Example: /// /// ```text /// #[serde_as] /// #[derive(Deserialize, Serialize)] -/// struct Example(#[serde_as(as = "Readable(Hex, _)")] [u8; 20]); +/// struct Example(#[serde_as(as = "Readable")] [u8; 20]); /// ``` /// -/// The above example will encode the byte array to Hex string for human-readable serializer +/// The above example will delegate human-readable serde to `DisplayFromStr` /// and array tuple (default) for non-human-readable serializer. -pub struct Readable { - element: PhantomData, - encoding: PhantomData, +pub struct Readable { + human_readable: PhantomData, + non_human_readable: PhantomData, } -impl SerializeAs for Readable +impl SerializeAs for Readable where - T: AsRef<[u8]>, + H: SerializeAs, R: SerializeAs, - E: SerializeAs, { fn serialize_as(value: &T, serializer: S) -> Result where S: Serializer, { if serializer.is_human_readable() { - E::serialize_as(value, serializer) + H::serialize_as(value, serializer) } else { R::serialize_as(value, serializer) } } } -/// DeserializeAs support for Arrays -impl<'de, R, E, const N: usize> DeserializeAs<'de, [u8; N]> for Readable + +impl<'de, R, H, T> DeserializeAs<'de, T> for Readable where - R: DeserializeAs<'de, [u8; N]>, - E: DeserializeAs<'de, Vec>, + H: DeserializeAs<'de, T>, + R: DeserializeAs<'de, T>, { - fn deserialize_as(deserializer: D) -> Result<[u8; N], D::Error> + fn deserialize_as(deserializer: D) -> Result where D: Deserializer<'de>, { if deserializer.is_human_readable() { - let value = E::deserialize_as(deserializer)?; - if value.len() != N { - return Err(Error::custom(anyhow!( - "invalid array length {}, expecting {}", - value.len(), - N - ))); - } - let mut array = [0u8; N]; - array.copy_from_slice(&value[..N]); - Ok(array) + H::deserialize_as(deserializer) } else { R::deserialize_as(deserializer) } } } -/// DeserializeAs support for Vec -impl<'de, R, E> DeserializeAs<'de, Vec> for Readable -where - R: DeserializeAs<'de, Vec>, - E: DeserializeAs<'de, Vec>, -{ - fn deserialize_as(deserializer: D) -> Result, D::Error> + +/// custom serde for AccountAddress +pub struct HexAccountAddress; + +impl SerializeAs for HexAccountAddress { + fn serialize_as(value: &AccountAddress, serializer: S) -> Result where - D: Deserializer<'de>, + S: Serializer, { - if deserializer.is_human_readable() { - E::deserialize_as(deserializer) - } else { - R::deserialize_as(deserializer) - } + Hex::serialize_as(value, serializer) } } -/// DeserializeAs support for AccountAddress -impl<'de, R, E> DeserializeAs<'de, AccountAddress> for Readable -where - R: DeserializeAs<'de, AccountAddress>, - E: Encoding, -{ +impl<'de> DeserializeAs<'de, AccountAddress> for HexAccountAddress { fn deserialize_as(deserializer: D) -> Result where D: Deserializer<'de>, { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - if s.starts_with("0x") { - AccountAddress::from_hex_literal(&s) - } else { - AccountAddress::from_hex(&s) - } - .map_err(to_custom_error::<'de, D, _>) + let s = String::deserialize(deserializer)?; + if s.starts_with("0x") { + AccountAddress::from_hex_literal(&s) } else { - R::deserialize_as(deserializer) + AccountAddress::from_hex(&s) } + .map_err(to_custom_error::<'de, D, _>) } } diff --git a/narwhal/crypto/Cargo.toml b/narwhal/crypto/Cargo.toml index ce1e8bcf9815b..caa04b18fb1df 100644 --- a/narwhal/crypto/Cargo.toml +++ b/narwhal/crypto/Cargo.toml @@ -13,7 +13,7 @@ ark-bls12-377 = { version = "0.3.0", features = ["std"], optional = true } eyre = "0.6.8" rand = { version = "0.8.5", features = ["std"] } serde = { version = "1.0.144", features = ["derive"] } -serde_bytes = "0.11.7" +serde_bytes = "0.11.9" serde_with = "2.1.0" tokio = { workspace = true, features = ["sync", "rt", "macros"] } zeroize = "1.5.7" diff --git a/sdk/typescript/src/providers/json-rpc-provider.ts b/sdk/typescript/src/providers/json-rpc-provider.ts index 3f91dc721008b..296decd2e9742 100644 --- a/sdk/typescript/src/providers/json-rpc-provider.ts +++ b/sdk/typescript/src/providers/json-rpc-provider.ts @@ -61,7 +61,7 @@ import { versionToString, } from '../types'; import { lt } from '@suchipi/femver'; -import { DynamicFieldPage } from '../types/dynamic_fields'; +import { DynamicFieldName, DynamicFieldPage } from '../types/dynamic_fields'; import { DEFAULT_CLIENT_OPTIONS, WebsocketClient, @@ -930,7 +930,7 @@ export class JsonRpcProvider extends Provider { async getDynamicFieldObject( parent_object_id: ObjectId, - name: string, + name: string | DynamicFieldName, ): Promise { try { const resp = await this.client.requestWithType( diff --git a/sdk/typescript/src/providers/provider.ts b/sdk/typescript/src/providers/provider.ts index f353c7403f505..c4f890afef410 100644 --- a/sdk/typescript/src/providers/provider.ts +++ b/sdk/typescript/src/providers/provider.ts @@ -47,7 +47,7 @@ import { CommitteeInfo, } from '../types'; -import { DynamicFieldPage } from '../types/dynamic_fields'; +import { DynamicFieldName, DynamicFieldPage } from '../types/dynamic_fields'; /////////////////////////////// // Exported Abstracts @@ -356,7 +356,7 @@ export abstract class Provider { */ abstract getDynamicFieldObject( parent_object_id: ObjectId, - name: string, + name: string | DynamicFieldName, ): Promise; /** diff --git a/sdk/typescript/src/providers/void-provider.ts b/sdk/typescript/src/providers/void-provider.ts index acc42fc1805af..89e972902675f 100644 --- a/sdk/typescript/src/providers/void-provider.ts +++ b/sdk/typescript/src/providers/void-provider.ts @@ -48,7 +48,7 @@ import { } from '../types'; import { Provider } from './provider'; -import { DynamicFieldPage } from '../types/dynamic_fields'; +import { DynamicFieldName, DynamicFieldPage } from '../types/dynamic_fields'; import { SerializedSignature } from '../cryptography/signature'; export class VoidProvider extends Provider { @@ -209,7 +209,7 @@ export class VoidProvider extends Provider { getDynamicFieldObject( _parent_object_id: ObjectId, - _name: string, + _name: string | DynamicFieldName, ): Promise { throw this.newError('getDynamicFieldObject'); } diff --git a/sdk/typescript/src/types/dynamic_fields.ts b/sdk/typescript/src/types/dynamic_fields.ts index d02c8ae96bc54..591f8ba63fd66 100644 --- a/sdk/typescript/src/types/dynamic_fields.ts +++ b/sdk/typescript/src/types/dynamic_fields.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { + any, array, Infer, literal, @@ -18,8 +19,14 @@ export const DynamicFieldType = union([ ]); export type DynamicFieldType = Infer; +export const DynamicFieldName = object({ + type: string(), + value: any(), +}); +export type DynamicFieldName = Infer; + export const DynamicFieldInfo = object({ - name: string(), + name: union([DynamicFieldName, string()]), type: DynamicFieldType, objectType: string(), objectId: ObjectId,