Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[moveos_std] Implement json::to_map #1194

Merged
merged 2 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/rooch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde_yaml = { workspace = true }
serde_json = { workspace = true }
once_cell = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
async-trait = { workspace = true }
codespan-reporting = { workspace = true }
termcolor = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/rooch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::process::exit;
/// rooch is a command line tools for Rooch Network
#[tokio::main]
async fn main() {
let _ = tracing_subscriber::fmt::try_init();

let opt = RoochCli::parse();
let result = rooch::run_cli(opt).await;

Expand Down
43 changes: 41 additions & 2 deletions moveos/moveos-stdlib/moveos-stdlib/doc/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,37 @@



- [Constants](#@Constants_0)
- [Function `from_json`](#0x2_json_from_json)
- [Function `to_map`](#0x2_json_to_map)


<pre><code><b>use</b> <a href="">0x1::string</a>;
<b>use</b> <a href="simple_map.md#0x2_simple_map">0x2::simple_map</a>;
</code></pre>



<a name="@Constants_0"></a>

## Constants


<a name="0x2_json_ErrorTypeNotMatch"></a>



<pre><code><b>const</b> <a href="json.md#0x2_json_ErrorTypeNotMatch">ErrorTypeNotMatch</a>: u64 = 1;
</code></pre>


<pre><code></code></pre>

<a name="0x2_json_ErrorInvalidJSONString"></a>



<pre><code><b>const</b> <a href="json.md#0x2_json_ErrorInvalidJSONString">ErrorInvalidJSONString</a>: u64 = 2;
</code></pre>



Expand All @@ -17,9 +44,21 @@
## Function `from_json`

Function to deserialize a type T.
Note the <code>private_generics</code> ensure only the <code>T</code>'s owner module can call this function
The u128 and u256 types must be json String type instead of Number type


<pre><code><b>public</b> <b>fun</b> <a href="json.md#0x2_json_from_json">from_json</a>&lt;T&gt;(json_str: <a href="">vector</a>&lt;u8&gt;): T
</code></pre>



<a name="0x2_json_to_map"></a>

## Function `to_map`

Parse a json object string to a SimpleMap
If the field type is primitive type, it will be parsed to String, otherwise it will abort.


<pre><code><b>public</b> <b>fun</b> <a href="json.md#0x2_json_to_map">to_map</a>(json_str: <a href="">vector</a>&lt;u8&gt;): <a href="simple_map.md#0x2_simple_map_SimpleMap">simple_map::SimpleMap</a>&lt;<a href="_String">string::String</a>, <a href="_String">string::String</a>&gt;
</code></pre>
34 changes: 31 additions & 3 deletions moveos/moveos-stdlib/moveos-stdlib/sources/json.move
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,34 @@
// SPDX-License-Identifier: Apache-2.0

module moveos_std::json{

use std::string::String;
use moveos_std::simple_map::SimpleMap;

const ErrorTypeNotMatch: u64 = 1;
const ErrorInvalidJSONString: u64 = 2;

#[private_generics(T)]
#[data_struct(T)]
/// Function to deserialize a type T.
/// Note the `private_generics` ensure only the `T`'s owner module can call this function
/// The u128 and u256 types must be json String type instead of Number type
public fun from_json<T>(json_str: vector<u8>): T {
native_from_json(json_str)
}

/// Parse a json object string to a SimpleMap
/// If the field type is primitive type, it will be parsed to String, otherwise it will abort.
public fun to_map(json_str: vector<u8>): SimpleMap<String,String>{
native_from_json<SimpleMap<String,String>>(json_str)
}

native fun native_from_json<T>(json_str: vector<u8>): T;

#[test_only]
use std::vector;
#[test_only]
use moveos_std::simple_map;
#[test_only]
use std::string;

#[test_only]
#[data_struct]
Expand All @@ -26,6 +40,8 @@ module moveos_std::json{
#[data_struct]
struct Test has copy, drop, store {
balance: u128,
ascii_string: std::ascii::String,
utf8_string: std::string::String,
age: u8,
inner: Inner,
bytes: vector<u8>,
Expand All @@ -35,7 +51,7 @@ module moveos_std::json{

#[test]
fun test_from_json() {
let json_str = b"{\"balance\": \"170141183460469231731687303715884105728\",\"age\":30,\"inner\":{\"value\":100},\"bytes\":[3,3,2,1],\"inner_array\":[{\"value\":101}],\"account\":\"0x42\"}";
let json_str = b"{\"balance\": \"170141183460469231731687303715884105728\",\"ascii_string\":\"rooch.network\",\"utf8_string\":\"rooch.network\",\"age\":30,\"inner\":{\"value\":100},\"bytes\":[3,3,2,1],\"inner_array\":[{\"value\":101}],\"account\":\"0x42\"}";
let obj = from_json<Test>(json_str);

assert!(obj.balance == 170141183460469231731687303715884105728u128, 1);
Expand All @@ -56,4 +72,16 @@ module moveos_std::json{
// check account
assert!(obj.account == @0x42, 11);
}

#[test]
fun test_to_map(){
let json_str = b"{\"balance\": \"170141183460469231731687303715884105728\",\"string\":\"rooch.network\",\"age\":30,\"bool_value\": true, \"null_value\": null, \"account\":\"0x42\"}";
let map = to_map(json_str);
assert!(simple_map::borrow(&map, &string::utf8(b"balance")) == &string::utf8(b"170141183460469231731687303715884105728"), 1);
assert!(simple_map::borrow(&map, &string::utf8(b"string")) == &string::utf8(b"rooch.network"), 2);
assert!(simple_map::borrow(&map, &string::utf8(b"age")) == &string::utf8(b"30"), 4);
assert!(simple_map::borrow(&map, &string::utf8(b"bool_value")) == &string::utf8(b"true"), 5);
assert!(simple_map::borrow(&map, &string::utf8(b"null_value")) == &string::utf8(b"null"), 6);
assert!(simple_map::borrow(&map, &string::utf8(b"account")) == &string::utf8(b"0x42"), 7);
}
}
116 changes: 91 additions & 25 deletions moveos/moveos-stdlib/src/natives/moveos_stdlib/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::natives::helpers::{make_module_natives, make_native};
use anyhow::Result;
use log::debug;
use move_binary_format::errors::{PartialVMError, PartialVMResult};
use move_core_types::account_address::AccountAddress;
use move_core_types::language_storage::TypeTag;
Expand All @@ -17,31 +18,99 @@ use move_vm_types::{
pop_arg,
values::{Struct, Value, Vector},
};
use moveos_types::addresses::MOVE_STD_ADDRESS;
use moveos_types::move_std::string::MoveString;
use moveos_types::moveos_std::simple_map::{Element, SimpleMap};
use moveos_types::state::{MoveStructType, MoveType};
use serde_json;
use smallvec::smallvec;
use std::collections::VecDeque;
use std::str::FromStr;

//TODO define more error code for convenience debug
const E_TYPE_NOT_MATCH: u64 = 1;
const E_INVALID_JSON_STRING: u64 = 2;

fn json_obj_to_key_value_pairs(json_obj: &serde_json::Value) -> Result<Vec<(String, String)>> {
if let serde_json::Value::Object(obj) = json_obj {
let mut key_value_pairs = Vec::new();
for (key, value) in obj.iter() {
let key = key.to_string();
let value = match value {
serde_json::Value::String(s) => s.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "null".to_string(),
_ => {
return Err(anyhow::anyhow!(
"Invalid json object, value of key {} is not string",
key
))
}
};
key_value_pairs.push((key, value));
}
Ok(key_value_pairs)
} else {
Err(anyhow::anyhow!("Invalid json object"))
}
}

fn parse_struct_value_from_json(
layout: &MoveStructLayout,
json_value: &serde_json::Value,
context: &NativeContext,
) -> Result<Struct> {
if let MoveStructLayout::WithTypes { fields, .. } = layout {
let field_values = fields
.iter()
.map(|field| -> Result<Value> {
let name = field.name.as_str();
let json_field = json_value
.get(name)
.ok_or_else(|| anyhow::anyhow!("Missing field {}", name))?;
parse_move_value_from_json(&field.layout, json_field, context)
})
.collect::<Result<Vec<Value>>>()?;
Ok(Struct::pack(field_values))
if let MoveStructLayout::WithTypes {
type_: struct_type,
fields,
} = layout
{
if struct_type.is_std_string(&MOVE_STD_ADDRESS) {
let str_value = json_value
.as_str()
.ok_or_else(|| anyhow::anyhow!("Invalid string value"))?;
Ok(Struct::pack(vec![Value::vector_u8(
str_value.as_bytes().to_vec(),
)]))
} else if struct_type.is_ascii_string(&MOVE_STD_ADDRESS) {
let str_value = json_value
.as_str()
.ok_or_else(|| anyhow::anyhow!("Invalid ascii string value"))?;
if !str_value.is_ascii() {
return Err(anyhow::anyhow!("Invalid ascii string value"));
}
Ok(Struct::pack(vec![Value::vector_u8(
str_value.as_bytes().to_vec(),
)]))
} else if struct_type == &SimpleMap::<MoveString, MoveString>::struct_tag() {
let key_value_pairs = json_obj_to_key_value_pairs(json_value)?;
let mut key_values = Vec::new();
for (key, value) in key_value_pairs {
key_values.push(Value::struct_(Struct::pack(vec![
Value::struct_(Struct::pack(vec![Value::vector_u8(
key.as_bytes().to_vec(),
)])),
Value::struct_(Struct::pack(vec![Value::vector_u8(
value.as_bytes().to_vec(),
)])),
])));
}
let element_type = context.load_type(&Element::<MoveString, MoveString>::type_tag())?;
Ok(Struct::pack(vec![Vector::pack(&element_type, key_values)?]))
} else {
let field_values = fields
.iter()
.map(|field| -> Result<Value> {
let name = field.name.as_str();
let json_field = json_value.get(name).ok_or_else(|| {
anyhow::anyhow!("type: {}, Missing field {}", struct_type, name)
})?;
parse_move_value_from_json(&field.layout, json_field, context)
})
.collect::<Result<Vec<Value>>>()?;
Ok(Struct::pack(field_values))
}
} else {
Err(anyhow::anyhow!("Invalid MoveStructLayout"))
}
Expand Down Expand Up @@ -104,14 +173,7 @@ fn parse_move_value_from_json(
let struct_value = parse_struct_value_from_json(struct_layout, json_value, context)?;
Ok(Value::struct_(struct_value))
}
MoveTypeLayout::Signer => {
let addr_str = json_value
.as_str()
.ok_or_else(|| anyhow::anyhow!("Invalid address value"))?;
let addr = AccountAddress::from_hex_literal(addr_str)
.map_err(|_| anyhow::anyhow!("Invalid address value"))?;
Ok(Value::signer(addr))
}
MoveTypeLayout::Signer => Err(anyhow::anyhow!("Do not support Signer type")),
MoveTypeLayout::U16 => {
let u64_value = json_value
.as_u64()
Expand Down Expand Up @@ -191,7 +253,8 @@ fn native_from_json(
};
let json_obj: serde_json::Value = match serde_json::from_str(json_str) {
Ok(obj) => obj,
Err(_) => {
Err(e) => {
debug!("Failed to parse json object: {:?}", e);
return Ok(NativeResult::err(
cost,
moveos_types::move_std::error::invalid_argument(E_INVALID_JSON_STRING),
Expand All @@ -203,10 +266,13 @@ fn native_from_json(
if let MoveTypeLayout::Struct(struct_layout) = layout {
match parse_struct_value_from_json(&struct_layout, &json_obj, context) {
Ok(val) => Ok(NativeResult::ok(cost, smallvec![Value::struct_(val)])),
Err(_) => Ok(NativeResult::err(
cost,
moveos_types::move_std::error::invalid_argument(E_INVALID_JSON_STRING),
)),
Err(e) => {
debug!("Failed to parse struct_value: {:?}", e);
Ok(NativeResult::err(
cost,
moveos_types::move_std::error::invalid_argument(E_INVALID_JSON_STRING),
))
}
}
} else {
Ok(NativeResult::err(
Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript/test/e2e/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ describe('SDK', () => {
expect(account).toBeDefined()

// create session account
const sessionAccount = await account.createSessionAccount(['0x3::empty::empty'], 100)
const sessionAccount = await account.createSessionAccount(['0x3::empty::empty'], 10000)
expect(sessionAccount).toBeDefined()

// run function with sessoin key
Expand Down
Loading