Skip to content

Commit

Permalink
Suijson u64 inputs must be strings (MystenLabs#6471)
Browse files Browse the repository at this point in the history
* u64 must be strings in sui json
  • Loading branch information
oxade authored Dec 13, 2022
1 parent 47bb8cd commit f944154
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 37 deletions.
12 changes: 6 additions & 6 deletions crates/sui-json-rpc/src/unit_tests/rpc_server_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async fn test_move_call() -> Result<(), anyhow::Error> {

let json_args = vec![
SuiJsonValue::from_object_id(coin.object_id),
SuiJsonValue::from_str("10")?,
SuiJsonValue::from_str("\"10\"")?,
];

let transaction_bytes: TransactionBytes = http_client
Expand Down Expand Up @@ -205,7 +205,7 @@ async fn test_get_object_info() -> Result<(), anyhow::Error> {
Ok(())
}

#[tokio::test]
#[sim_test]
async fn test_get_coins() -> Result<(), anyhow::Error> {
let port = get_available_port();
let cluster = TestClusterBuilder::new()
Expand Down Expand Up @@ -251,7 +251,7 @@ async fn test_get_coins() -> Result<(), anyhow::Error> {
Ok(())
}

#[tokio::test]
#[sim_test]
async fn test_get_balances() -> Result<(), anyhow::Error> {
let port = get_available_port();
let cluster = TestClusterBuilder::new()
Expand All @@ -270,7 +270,7 @@ async fn test_get_balances() -> Result<(), anyhow::Error> {
Ok(())
}

#[tokio::test]
#[sim_test]
async fn test_get_metadata() -> Result<(), anyhow::Error> {
let cluster = TestClusterBuilder::new().build().await?;

Expand Down Expand Up @@ -333,7 +333,7 @@ async fn test_get_metadata() -> Result<(), anyhow::Error> {
Ok(())
}

#[tokio::test]
#[sim_test]
async fn test_get_total_supply() -> Result<(), anyhow::Error> {
let cluster = TestClusterBuilder::new().build().await?;

Expand Down Expand Up @@ -425,7 +425,7 @@ async fn test_get_total_supply() -> Result<(), anyhow::Error> {
vec![coin_type.into()],
vec![
SuiJsonValue::from_str(&treasury_cap.to_string()).unwrap(),
SuiJsonValue::from_str("100000").unwrap(),
SuiJsonValue::from_str("\"100000\"").unwrap(),
SuiJsonValue::from_str(&address.to_string()).unwrap(),
],
Some(gas.object_id),
Expand Down
14 changes: 5 additions & 9 deletions crates/sui-json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ impl SuiJsonValue {
JsonValue::Number(n) => {
// Must be castable to u64
if !n.is_u64() {
return Err(anyhow!("{n} not allowed. Number must be unsigned integer"));
return Err(anyhow!(
"{n} not allowed. Number must be unsigned integer of at most u32"
));
}
}
// Must be homogeneous
Expand Down Expand Up @@ -166,7 +168,7 @@ impl SuiJsonValue {
// Bool to Bool is simple
(JsonValue::Bool(b), MoveTypeLayout::Bool) => MoveValue::Bool(*b),

// In constructor, we have already checked that the JSON number is unsigned int of at most U64
// In constructor, we have already checked that the JSON number is unsigned int of at most U32
(JsonValue::Number(n), MoveTypeLayout::U8) => match n.as_u64() {
Some(x) => MoveValue::U8(u8::try_from(x)?),
None => return Err(anyhow!("{} is not a valid number. Only u8 allowed.", n)),
Expand All @@ -179,10 +181,6 @@ impl SuiJsonValue {
Some(x) => MoveValue::U32(u32::try_from(x)?),
None => return Err(anyhow!("{} is not a valid number. Only u32 allowed.", n)),
},
(JsonValue::Number(n), MoveTypeLayout::U64) => match n.as_u64() {
Some(x) => MoveValue::U64(x),
None => return Err(anyhow!("{} is not a valid number. Only u64 allowed.", n)),
},

// u8, u16, u32, u64, u128, u256 can be encoded as String
(JsonValue::String(s), MoveTypeLayout::U8) => {
Expand Down Expand Up @@ -275,11 +273,9 @@ fn try_from_bcs_bytes(bytes: &[u8]) -> Result<JsonValue, anyhow::Error> {
Ok(JsonValue::Number(Number::from(v)))
} else if let Ok(v) = bcs::from_bytes::<u32>(bytes) {
Ok(JsonValue::Number(Number::from(v)))
} else if let Ok(v) = bcs::from_bytes::<u64>(bytes) {
Ok(JsonValue::Number(Number::from(v)))
} else if let Ok(v) = bcs::from_bytes::<bool>(bytes) {
Ok(JsonValue::Bool(v))
} else if let Ok(v) = bcs::from_bytes::<Vec<u64>>(bytes) {
} else if let Ok(v) = bcs::from_bytes::<Vec<u32>>(bytes) {
let v = v
.into_iter()
.map(|v| JsonValue::Number(Number::from(v)))
Expand Down
23 changes: 11 additions & 12 deletions crates/sui-json/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ fn test_basic_args_linter_pure_args_good() {
),
// U64 nest
(
json!([[1111, 2, 3], [], [300, 4, 5, 6, 7]]),
json!([["1111", "2", "3"], [], ["300", "4", "5", "6", "7"]]),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(
MoveTypeLayout::U64,
)))),
Expand All @@ -370,16 +370,16 @@ fn test_basic_args_linter_pure_args_good() {
])
.unwrap(),
),
// U64 deep nest, good
// U32 deep nest, good
(
json!([[[9, 53, 434], [0], [300]], [], [[332], [4, 5, 6, 7]]]),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U64)),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U32)),
)))),
bcs::to_bytes(&vec![
vec![vec![9u64, 53u64, 434u64], vec![0u64], vec![300u64]],
vec![vec![9u32, 53u32, 434u32], vec![0u32], vec![300u32]],
vec![],
vec![vec![332u64], vec![4u64, 5u64, 6u64, 7u64]],
vec![vec![332u32], vec![4u32, 5u32, 6u32, 7u32]],
])
.unwrap(),
),
Expand Down Expand Up @@ -426,7 +426,7 @@ fn test_basic_args_linter_top_level() {
*/

let monster_name_raw = "MonsterName";
let monster_img_id_raw = 12345678;
let monster_img_id_raw = "12345678";
let breed_raw = 89;
let monster_affinity_raw = 200;
let monster_description_raw = "MonsterDescription";
Expand Down Expand Up @@ -472,7 +472,7 @@ fn test_basic_args_linter_top_level() {
);
assert_eq!(
json_args[2],
SuiJsonCallArg::Pure(bcs::to_bytes(&(monster_img_id_raw as u64)).unwrap()),
SuiJsonCallArg::Pure(bcs::to_bytes(&(monster_img_id_raw.parse::<u64>().unwrap())).unwrap()),
);
assert_eq!(
json_args[3],
Expand Down Expand Up @@ -516,7 +516,7 @@ fn test_basic_args_linter_top_level() {
Function signature:
public fun create(value: u64, recipient: vector<u8>, ctx: &mut TxContext)
*/
let value_raw = 29897;
let value_raw = "29897";
let address = SuiAddress::random_for_testing_only();

let value = json!(value_raw);
Expand All @@ -533,7 +533,7 @@ fn test_basic_args_linter_top_level() {

assert_eq!(
args[0],
SuiJsonCallArg::Pure(bcs::to_bytes(&(value_raw as u64)).unwrap())
SuiJsonCallArg::Pure(bcs::to_bytes(&(value_raw.parse::<u64>().unwrap())).unwrap())
);

// Need to verify this specially
Expand Down Expand Up @@ -636,16 +636,15 @@ fn test_convert_address_from_bcs() {

#[test]
fn test_convert_number_from_bcs() {
let bcs_bytes = [160u8, 134, 1, 0, 0, 0, 0, 0];
let bcs_bytes = [160u8, 134, 1, 0];
let value = SuiJsonValue::from_bcs_bytes(&bcs_bytes).unwrap();
assert_eq!(100000, value.0.as_u64().unwrap());
}

#[test]
fn test_convert_number_array_from_bcs() {
let bcs_bytes = [
5, 80, 195, 0, 0, 0, 0, 0, 0, 80, 195, 0, 0, 0, 0, 0, 0, 80, 195, 0, 0, 0, 0, 0, 0, 80,
195, 0, 0, 0, 0, 0, 0, 80, 195, 0, 0, 0, 0, 0, 0,
5, 80, 195, 0, 0, 80, 195, 0, 0, 80, 195, 0, 0, 80, 195, 0, 0, 80, 195, 0, 0,
];

let value = SuiJsonValue::from_bcs_bytes(&bcs_bytes).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/sui/src/unit_tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ async fn test_move_call_args_linter_command() -> Result<(), anyhow::Error> {

// Create the args
let args = vec![
SuiJsonValue::new(json!(123u8))?,
SuiJsonValue::new(json!("123"))?,
SuiJsonValue::new(json!(address1))?,
];

Expand Down
8 changes: 4 additions & 4 deletions doc/src/build/sui-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This table shows the restrictions placed on JSON types to make them SuiJSON comp

| JSON | SuiJSON Restrictions | Move Type Mapping |
|---------|----------------------------------------------|-------------------------------------------------------------------------------------------------|
| Number | Must be unsigned integer | U8<br>U16<br>U32<br>U64<br>(U128 is encoded as String)<br>(U256 is encoded as String) |
| Number | Must be unsigned integer | U8<br>U16<br>U32<br>(U64 is encoded as String)<br>(U128 is encoded as String)<br>(U256 is encoded as String) |
| String | No restrictions | Vector&lt;U8><br>Address<br>ObjectID<br>TypeTag<br>Identifier<br>Unsigned Integer (256 bit max) |
| Boolean | No restrictions | Bool |
| Array | Must be homogeneous JSON and of SuiJSON type | Vector |
Expand All @@ -20,7 +20,7 @@ This table shows the restrictions placed on JSON types to make them SuiJSON comp
## Type coercion reasoning

Due to the loosely typed nature of JSON/SuiJSON and the strongly typed nature of Move types, we sometimes need to overload SuiJSON types to represent multiple Move types. \
For example `SuiJSON::Number` can represent both *U8* and *U64*. This means we have to coerce and sometimes convert types.
For example `SuiJSON::Number` can represent both *U8* and *U32*. This means we have to coerce and sometimes convert types.

Which type we coerce depends on the expected Move type. For example, if the Move function expects a U8, we must have received a `SuiJSON::Number` with a value less than 256. More importantly, we have no way to easily express Move addresses in JSON, so we encode them as hex strings prefixed by `0x`.

Expand All @@ -34,12 +34,12 @@ Additionally, Move supports U128 and U256 but JSON doesn't. As a result we allow
| U8 | Supports three formats<ul><li>Unsigned number &lt; 256. </li><li>Decimal string with value &lt; 256.</li><li>One byte hex string prefixed with `0x`.</li></ul> | `7`<br>`"70"`<br>`"0x43"` | `-5`: negative not allowed<br>`3.9`: float not allowed<br>`NaN`: not allowed<br>`300`: U8 must be less than 256<br>`" 9"`: Spaces not allowed in string<br>`"9A"`: Hex num must be prefixed with `0x`<br>`"0x09CD"`: Too large for U8 |
| U16 | Three formats are supported<ul><li>Unsigned number &lt; 65536. </li><li>Decimal string with value &lt; 65536.</li><li>Two byte hex string prefixed with `0x`.</li></ul> | `712`<br>`"570"`<br>`"0x423"` | `-5`: negative not allowed<br>`3.9`: float not allowed<br>`NaN`: not allowed<br>`98342300`: U16 must be less than 65536<br>`" 19"`: Spaces not allowed in string<br>`"9EA"`: Hex num must be prefixed with `0x`<br>`"0x049C1D"`: Too large for U16
| U32 | Three formats are supported<ul><li>Unsigned number &lt; 4294967296. </li><li>Decimal string with value &lt; 4294967296.</li><li>One byte hex string prefixed with `0x`.</li></ul> | `9823247`<br>`"987120"`<br>`"0x4BADE93"` | `-5`: negative not allowed<br>`3.9`: float not allowed<br>`NaN`: not allowed<br>`123456789123456`: U32 must be less than 4294967296<br>`" 9"`: Spaces not allowed in string<br>`"9A"`: Hex num must be prefixed with `0x`<br>`"0x3FF1FF9FFDEFF"`: Too large for U32
| U64 | Similarly to U8, three formats are supported<ul><li>Unsigned number &lt; U64::MAX.</li><li>Decimal string with value &lt; U64::MAX.</li><li>Up to 8 byte hex string prefixed with `0x`.</li></ul> | Extrapolate above examples | Extrapolate above examples |
| U64 | Supports two formats<ul><li>Decimal string with value &lt; U64::MAX.</li><li>Up to 8 byte hex string prefixed with `0x`.</li></ul> | `"747944370"`<br>`"0x2B1A39A15E"` | `123434`: Although this is a valid U64 number, it must be encoded as a string |
| U128 | Supports two formats<ul><li>Decimal string with value &lt; U128::MAX.</li><li>Up to 16 byte hex string prefixed with `0x`.</li></ul> | `"74794734937420002470"`<br>`"0x2B1A39A1514E1D8A7CE"` | `34`: Although this is a valid U128 number, it must be encoded as a string |
| U256 | Two formats are supported<ul><li>Decimal string with value &lt; U256::MAX.</li><li>Up to 32 byte hex string prefixed with `0x`.</li></ul> | `"747947349374200024707479473493742000247"`<br>`"0x2B1762FECADA39753FCAB2A1514E1D8A7CE"` | `123434`: Although this is a valid U256 number, it must be encoded as a string |
| 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 | 20 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 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;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 U32 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 ||

Loading

0 comments on commit f944154

Please sign in to comment.