diff --git a/crates/sui-core/src/gateway_state.rs b/crates/sui-core/src/gateway_state.rs index 287e6b252ba57..49571285e7747 100644 --- a/crates/sui-core/src/gateway_state.rs +++ b/crates/sui-core/src/gateway_state.rs @@ -1151,6 +1151,21 @@ where })) } + async fn get_object_arg( + &self, + id: ObjectID, + objects: &mut BTreeMap, + ) -> Result { + let obj = self.get_object_internal(&id).await?; + let arg = if obj.is_shared() { + ObjectArg::SharedObject(id) + } else { + ObjectArg::ImmOrOwnedObject(obj.compute_object_reference()) + }; + objects.insert(id, obj); + Ok(arg) + } + async fn create_move_call_transaction_kind( &self, params: MoveCallParams, @@ -1181,16 +1196,16 @@ where for json_arg in json_args { args.push(match json_arg { SuiJsonCallArg::Object(id) => { - let obj = self.get_object_internal(&id).await?; - let arg = if obj.is_shared() { - CallArg::Object(ObjectArg::SharedObject(id)) - } else { - CallArg::Object(ObjectArg::ImmOrOwnedObject(obj.compute_object_reference())) - }; - objects.insert(id, obj); - arg + CallArg::Object(self.get_object_arg(id, &mut objects).await?) } SuiJsonCallArg::Pure(bytes) => CallArg::Pure(bytes), + SuiJsonCallArg::ObjVec(v) => { + let mut object_ids = vec![]; + for id in v { + object_ids.push(self.get_object_arg(id, &mut objects).await?); + } + CallArg::ObjVec(object_ids) + } }) } diff --git a/crates/sui-json/src/lib.rs b/crates/sui-json/src/lib.rs index cc5419389cca8..55980c810599c 100644 --- a/crates/sui-json/src/lib.rs +++ b/crates/sui-json/src/lib.rs @@ -30,6 +30,8 @@ pub enum SuiJsonCallArg { Object(ObjectID), // pure value, bcs encoded Pure(Vec), + // a vector of objects + ObjVec(Vec), } #[derive(Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema)] @@ -286,9 +288,9 @@ fn make_prim_move_type_layout(param: &SignatureToken) -> Result Result { +fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result { // Every elem has to be a string convertible to a ObjectID - match arg.to_json_value() { + match arg { JsonValue::String(s) => { let s = s.trim().to_lowercase(); if !s.starts_with(HEX_PREFIX) { @@ -297,11 +299,31 @@ fn resolve_object_arg(idx: usize, arg: &SuiJsonValue) -> Result Err(anyhow!( - "Unable to parse arg {:?} as ObjectID at pos {}. Expected {:?} byte hex string \ + "Unable to parse arg {:?} as ObjectID at pos {}. Expected {:?}-byte hex string \ prefixed with 0x.", - ObjectID::LENGTH, + arg, idx, + ObjectID::LENGTH, + )), + } +} + +fn resolve_object_vec_arg(idx: usize, arg: &SuiJsonValue) -> Result, anyhow::Error> { + // Every elem has to be a string convertible to a ObjectID + match arg.to_json_value() { + JsonValue::Array(a) => { + let mut object_ids = vec![]; + for id in a { + object_ids.push(resolve_object_arg(idx, &id)?); + } + Ok(object_ids) + } + _ => Err(anyhow!( + "Unable to parse arg {:?} as vector of ObjectIDs at pos {}. \ + Expected a vector of {:?}-byte hex strings prefixed with 0x.", arg.to_json_value(), + idx, + ObjectID::LENGTH, )), } } @@ -316,15 +338,26 @@ fn resolve_call_arg( | SignatureToken::U8 | SignatureToken::U64 | SignatureToken::U128 - | SignatureToken::Address - | SignatureToken::Vector(_) => SuiJsonCallArg::Pure(resolve_primtive_arg(arg, param)?), + | SignatureToken::Address => SuiJsonCallArg::Pure(resolve_primtive_arg(arg, param)?), SignatureToken::Struct(_) | SignatureToken::StructInstantiation(_, _) | SignatureToken::TypeParameter(_) | SignatureToken::Reference(_) | SignatureToken::MutableReference(_) => { - SuiJsonCallArg::Object(resolve_object_arg(idx, arg)?) + SuiJsonCallArg::Object(resolve_object_arg(idx, &arg.to_json_value())?) + } + + SignatureToken::Vector(inner) => { + match **inner { + // in terms of non-primitive vectors we only currently support vectors of objects + // (but not, for example, vectors of references), so vector content are either + // objects of we assume that they are primitive (and throw an error if not) + SignatureToken::Struct(_) => { + SuiJsonCallArg::ObjVec(resolve_object_vec_arg(idx, arg)?) + } + _ => SuiJsonCallArg::Pure(resolve_primtive_arg(arg, param)?), + } } SignatureToken::Signer => unreachable!(), diff --git a/crates/sui-json/src/tests.rs b/crates/sui-json/src/tests.rs index 731f290ccc58a..bf74f6334549a 100644 --- a/crates/sui-json/src/tests.rs +++ b/crates/sui-json/src/tests.rs @@ -475,6 +475,45 @@ fn test_basic_args_linter_top_level() { args[1], SuiJsonCallArg::Pure(bcs::to_bytes(&AccountAddress::from(address)).unwrap()) ); + + // Test with object vector args + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../sui-core/src/unit_tests/data/entry_point_vector"); + let compiled_modules = + sui_framework::build_and_verify_package(&path, move_package::BuildConfig::default()) + .unwrap(); + let example_package = Object::new_package(compiled_modules, TransactionDigest::genesis()); + let example_package = example_package.data.try_as_package().unwrap(); + + let module = Identifier::new("entry_point_vector").unwrap(); + let function = Identifier::new("two_obj_vec_destroy").unwrap(); + + /* + Function signature: + public entry fun two_obj_vec_destroy(v: vector, _: &mut TxContext) + */ + let object_id_raw1 = ObjectID::random(); + let object_id_raw2 = ObjectID::random(); + let object_id1 = json!(format!("0x{:02x}", object_id_raw1)); + let object_id2 = json!(format!("0x{:02x}", object_id_raw2)); + + let args = vec![SuiJsonValue::new(Value::Array(vec![object_id1, object_id2])).unwrap()]; + + let args = resolve_move_function_args(example_package, module, function, args).unwrap(); + + assert!(matches!(args[0], SuiJsonCallArg::ObjVec { .. })); + + if let SuiJsonCallArg::ObjVec(vec) = &args[0] { + assert!(vec.len() == 2); + assert_eq!( + vec[0], + ObjectID::from_hex_literal(&format!("0x{:02x}", object_id_raw1)).unwrap() + ); + assert_eq!( + vec[1], + ObjectID::from_hex_literal(&format!("0x{:02x}", object_id_raw2)).unwrap() + ); + } } #[test] diff --git a/crates/sui-sdk/src/transaction_builder.rs b/crates/sui-sdk/src/transaction_builder.rs index 4b25d6cc7a8c4..43d920a4bf6ca 100644 --- a/crates/sui-sdk/src/transaction_builder.rs +++ b/crates/sui-sdk/src/transaction_builder.rs @@ -171,6 +171,23 @@ impl TransactionBuilder { })) } + async fn get_object_arg( + &self, + id: ObjectID, + objects: &mut BTreeMap, + ) -> Result { + let response = self.0.get_object(id).await?; + let obj: Object = response.into_object()?.try_into()?; + let obj_ref = obj.compute_object_reference(); + let owner = obj.owner; + objects.insert(id, obj); + Ok(if owner.is_shared() { + ObjectArg::SharedObject(id) + } else { + ObjectArg::ImmOrOwnedObject(obj_ref) + }) + } + async fn resolve_and_checks_json_args( &self, package_id: ObjectID, @@ -193,19 +210,17 @@ impl TransactionBuilder { let mut objects = BTreeMap::new(); for arg in json_args { args.push(match arg { - SuiJsonCallArg::Object(o) => { - let response = self.0.get_object(o).await?; - let obj: Object = response.into_object()?.try_into()?; - let obj_ref = obj.compute_object_reference(); - let owner = obj.owner; - objects.insert(o, obj); - if owner.is_shared() { - CallArg::Object(ObjectArg::SharedObject(o)) - } else { - CallArg::Object(ObjectArg::ImmOrOwnedObject(obj_ref)) - } + SuiJsonCallArg::Object(id) => { + CallArg::Object(self.get_object_arg(id, &mut objects).await?) } SuiJsonCallArg::Pure(p) => CallArg::Pure(p), + SuiJsonCallArg::ObjVec(v) => { + let mut object_ids = vec![]; + for id in v { + object_ids.push(self.get_object_arg(id, &mut objects).await?); + } + CallArg::ObjVec(object_ids) + } }) } let compiled_module = package.deserialize_module(module)?; diff --git a/doc/src/build/cli-client.md b/doc/src/build/cli-client.md index 0371653f12b7a..3f345a3a0b7f1 100644 --- a/doc/src/build/cli-client.md +++ b/doc/src/build/cli-client.md @@ -959,6 +959,16 @@ Note the third argument to the `transfer` function representing is a required argument for all functions callable from Sui and is auto-injected by the platform at the point of a function call. +> **Important:** If you use a shell that interprets square brackets ([ ]) as special characters (such as the `zsh` shell), you must enclose the brackets in single quotes. For example, instead of `[7,42]` you must use `'[7,42]'`. +> +> Additionally, when you specify a vector of object IDs, you must enclose each ID in double quotes. For example, +> `'["0x471c8e241d0473c34753461529b70f9c4ed3151b","0x53b50e3020a01e1fd6acf832a871feee240183f0"]'` + +To gain a deeper view into the object, include the +> `--json` flag in the `sui client` command to see the raw JSON representation +> of the object. + + The output of the call command is a bit verbose, but the important information that should be printed at the end indicates objects changes as a result of the function call: diff --git a/doc/src/build/sui-json.md b/doc/src/build/sui-json.md index de82c72c50247..9f6d12fa8d1ef 100644 --- a/doc/src/build/sui-json.md +++ b/doc/src/build/sui-json.md @@ -37,6 +37,6 @@ Additionally, Move supports U128 but JSON doesn't. As a result we allow encoding | Address | 20 byte hex string prefixed with `0x` | `"0x2B1A39A1514E1D8A7CE45919CFEB4FEE70B4E011"` | `0x2B1A39`: string too short
`2B1A39A1514E1D8A7CE45919CFEB4FEE70B4E011`: missing `0x` prefix
`0xG2B1A39A1514E1D8A7CE45919CFEB4FEE70B4E01`: invalid hex char `G` | | ObjectID | 16 byte hex string prefixed with `0x` | `"0x2B1A39A1514E1D8A7CE45919CFEB4FEE"` | Similar to above | | Identifier | Typically used for module and function names. Encoded as one of the following:
  1. A String whose first character is a letter and the remaining characters are letters, digits or underscore.
  2. A String whose first character is an underscore, and there is at least one further letter, digit or underscore
| `"function"`,
`"_function"`,
`"some_name"`,
`"\___\_some_name"`,
`"Another"` | `"_"`: missing trailing underscore, digit or letter,
`"8name"`: cannot start with digit,
`".function"`: cannot start with period,
`" "`: cannot be empty space,
`"func name"`: cannot have spaces | -| Vector<Move Type> | Homogeneous vector of aforementioned types including nested vectors | `[1,2,3,4]`: simple U8 vector
`[[3,600],[],[0,7,4]]`: nested U64 vector | `[1,2,3,false]`: not homogeneous JSON
`[1,2,null,4]`: invalid elements
`[1,2,"7"]`: although we allow encoding numbers as strings meaning this array can evaluate to `[1,2,7]`, the array is still ambiguous so it fails the homogeneity check. | +| Vector<Move Type> | Homogeneous vector of aforementioned types including nested vectors of primitive types (only "flat" vectors of ObjectIDs are allowed) | `[1,2,3,4]`: simple U8 vector
`[[3,600],[],[0,7,4]]`: nested U64 vector `["0x2B1A39A1514E1D8A7CE45919CFEB4FEE", "0x2B1A39A1514E1D8A7CE45919CFEB4FEF"]`: ObjectID vector | `[1,2,3,false]`: not homogeneous JSON
`[1,2,null,4]`: invalid elements
`[1,2,"7"]`: although we allow encoding numbers as strings meaning this array can evaluate to `[1,2,7]`, the array is still ambiguous so it fails the homogeneity check. | | Vector<U8> | For convenience, we allow:
U8 vectors represented as UTF-8 (and ASCII) strings. | `"√®ˆbo72 √∂†∆˚–œ∑π2ie"`: UTF-8
`"abcdE738-2 _=?"`: ASCII ||