Skip to content

Commit

Permalink
[Move] Added support for object vectors to Sui JSON (MystenLabs#4526)
Browse files Browse the repository at this point in the history
* [Move] Added support for object vectors to Sui JSON

* Update doc/src/build/cli-client.md

Co-authored-by: Randall-Mysten <[email protected]>

* Update doc/src/build/cli-client.md

Co-authored-by: Randall-Mysten <[email protected]>

Co-authored-by: Randall-Mysten <[email protected]>
  • Loading branch information
awelc and randall-Mysten authored Sep 8, 2022
1 parent 612f880 commit f0d850d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 27 deletions.
31 changes: 23 additions & 8 deletions crates/sui-core/src/gateway_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,21 @@ where
}))
}

async fn get_object_arg(
&self,
id: ObjectID,
objects: &mut BTreeMap<ObjectID, Object>,
) -> Result<ObjectArg, anyhow::Error> {
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,
Expand Down Expand Up @@ -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)
}
})
}

Expand Down
47 changes: 40 additions & 7 deletions crates/sui-json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum SuiJsonCallArg {
Object(ObjectID),
// pure value, bcs encoded
Pure(Vec<u8>),
// a vector of objects
ObjVec(Vec<ObjectID>),
}

#[derive(Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
Expand Down Expand Up @@ -286,9 +288,9 @@ fn make_prim_move_type_layout(param: &SignatureToken) -> Result<MoveTypeLayout,
})
}

fn resolve_object_arg(idx: usize, arg: &SuiJsonValue) -> Result<ObjectID, anyhow::Error> {
fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result<ObjectID, anyhow::Error> {
// 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) {
Expand All @@ -297,11 +299,31 @@ fn resolve_object_arg(idx: usize, arg: &SuiJsonValue) -> Result<ObjectID, anyhow
Ok(ObjectID::from_hex_literal(&s)?)
}
_ => 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<Vec<ObjectID>, 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,
)),
}
}
Expand All @@ -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!(),
Expand Down
39 changes: 39 additions & 0 deletions crates/sui-json/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Obj>, _: &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]
Expand Down
37 changes: 26 additions & 11 deletions crates/sui-sdk/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ impl TransactionBuilder {
}))
}

async fn get_object_arg(
&self,
id: ObjectID,
objects: &mut BTreeMap<ObjectID, Object>,
) -> Result<ObjectArg, anyhow::Error> {
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,
Expand All @@ -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)?;
Expand Down
10 changes: 10 additions & 0 deletions doc/src/build/cli-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion doc/src/build/sui-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>`2B1A39A1514E1D8A7CE45919CFEB4FEE70B4E011`: missing `0x` prefix<br>`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:<ol><li>A String whose first character is a letter and the remaining characters are letters, digits or underscore.</li><li>A String whose first character is an underscore, and there is at least one further letter, digit or underscore</li></ol> | `"function"`,<br>`"_function"`,<br>`"some_name"`,<br>`"\___\_some_name"`,<br>`"Another"` | `"_"`: missing trailing underscore, digit or letter,<br>`"8name"`: cannot start with digit,<br>`".function"`: cannot start with period,<br>`" "`: cannot be empty space,<br>`"func name"`: cannot have spaces |
| Vector&lt;Move Type> | Homogeneous vector of aforementioned types including nested vectors | `[1,2,3,4]`: simple U8 vector<br>`[[3,600],[],[0,7,4]]`: nested U64 vector | `[1,2,3,false]`: not homogeneous JSON<br>`[1,2,null,4]`: invalid elements<br>`[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&lt;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<br>`[[3,600],[],[0,7,4]]`: nested U64 vector `["0x2B1A39A1514E1D8A7CE45919CFEB4FEE", "0x2B1A39A1514E1D8A7CE45919CFEB4FEF"]`: ObjectID vector | `[1,2,3,false]`: not homogeneous JSON<br>`[1,2,null,4]`: invalid elements<br>`[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&lt;U8> | <em>For convenience, we allow:</em><br>U8 vectors represented as UTF-8 (and ASCII) strings. | `"√®ˆbo72 √∂†∆˚–œ∑π2ie"`: UTF-8<br>`"abcdE738-2 _=?"`: ASCII ||

0 comments on commit f0d850d

Please sign in to comment.