sui_json/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::{BTreeMap, VecDeque};
5use std::fmt::{self, Debug, Formatter};
6use std::str::FromStr;
7
8use anyhow::{anyhow, bail};
9use fastcrypto::encoding::{Encoding, Hex};
10use move_binary_format::CompiledModule;
11use move_binary_format::{binary_config::BinaryConfig, file_format::SignatureToken};
12use move_bytecode_utils::resolve_struct;
13pub use move_core_types::annotated_value::MoveTypeLayout;
14use move_core_types::annotated_value::{MoveFieldLayout, MoveVariant};
15use move_core_types::u256::U256;
16use move_core_types::{
17    annotated_value::{MoveStruct, MoveValue},
18    identifier::Identifier,
19    language_storage::{StructTag, TypeTag},
20    runtime_value as R,
21};
22use schemars::JsonSchema;
23use serde::{Deserialize, Serialize};
24use serde_json::{Number, Value as JsonValue, json};
25
26use sui_types::MOVE_STDLIB_ADDRESS;
27use sui_types::base_types::{
28    ObjectID, RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR, STD_ASCII_MODULE_NAME,
29    STD_ASCII_STRUCT_NAME, STD_OPTION_MODULE_NAME, STD_OPTION_STRUCT_NAME, STD_UTF8_MODULE_NAME,
30    STD_UTF8_STRUCT_NAME, SuiAddress, TxContext, TxContextKind, is_primitive_type_tag,
31    move_ascii_str_layout, move_utf8_str_layout,
32};
33use sui_types::id::{self, ID, RESOLVED_SUI_ID};
34use sui_types::move_package::MovePackage;
35use sui_types::object::bounded_visitor::BoundedVisitor;
36use sui_types::transfer::RESOLVED_RECEIVING_STRUCT;
37
38const HEX_PREFIX: &str = "0x";
39
40#[cfg(test)]
41mod tests;
42
43/// A list of error categories encountered when parsing numbers.
44#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
45pub enum SuiJsonValueErrorKind {
46    /// JSON value must be of specific types.
47    ValueTypeNotAllowed,
48
49    /// JSON arrays must be homogeneous.
50    ArrayNotHomogeneous,
51}
52
53#[derive(Debug)]
54pub struct SuiJsonValueError {
55    kind: SuiJsonValueErrorKind,
56    val: JsonValue,
57}
58
59impl SuiJsonValueError {
60    pub fn new(val: &JsonValue, kind: SuiJsonValueErrorKind) -> Self {
61        Self {
62            kind,
63            val: val.clone(),
64        }
65    }
66}
67
68impl std::error::Error for SuiJsonValueError {}
69
70impl fmt::Display for SuiJsonValueError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        let err_str = match self.kind {
73            SuiJsonValueErrorKind::ValueTypeNotAllowed => {
74                format!("JSON value type {} not allowed.", self.val)
75            }
76            SuiJsonValueErrorKind::ArrayNotHomogeneous => {
77                format!("Array not homogeneous. Mismatched value: {}.", self.val)
78            }
79        };
80        write!(f, "{err_str}")
81    }
82}
83
84// Intermediate type to hold resolved args
85#[derive(Eq, PartialEq, Debug)]
86pub enum ResolvedCallArg {
87    Object(ObjectID),
88    Pure(Vec<u8>),
89    ObjVec(Vec<ObjectID>),
90}
91
92#[derive(Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
93pub struct SuiJsonValue(JsonValue);
94impl SuiJsonValue {
95    pub fn new(json_value: JsonValue) -> Result<SuiJsonValue, anyhow::Error> {
96        Self::check_value(&json_value)?;
97        Ok(Self(json_value))
98    }
99
100    fn check_value(json_value: &JsonValue) -> Result<(), anyhow::Error> {
101        match json_value {
102            // No checks needed for Bool and String
103            JsonValue::Bool(_) | JsonValue::String(_) => (),
104            JsonValue::Number(n) => {
105                // Must be castable to u64
106                if !n.is_u64() {
107                    return Err(anyhow!(
108                        "{n} not allowed. Number must be unsigned integer of at most u32"
109                    ));
110                }
111            }
112            // Must be homogeneous
113            JsonValue::Array(a) => {
114                // Fail if not homogeneous
115                check_valid_homogeneous(&JsonValue::Array(a.to_vec()))?
116            }
117            JsonValue::Object(v) => {
118                for (_, value) in v {
119                    Self::check_value(value)?;
120                }
121            }
122            JsonValue::Null => bail!("Null not allowed."),
123        };
124        Ok(())
125    }
126
127    pub fn from_object_id(id: ObjectID) -> SuiJsonValue {
128        Self(JsonValue::String(id.to_hex_uncompressed()))
129    }
130
131    pub fn to_bcs_bytes(&self, ty: &MoveTypeLayout) -> Result<Vec<u8>, anyhow::Error> {
132        let move_value = Self::to_move_value(&self.0, ty)?;
133        R::MoveValue::simple_serialize(&move_value)
134            .ok_or_else(|| anyhow!("Unable to serialize {:?}. Expected {}", move_value, ty))
135    }
136
137    pub fn from_bcs_bytes(
138        layout: Option<&MoveTypeLayout>,
139        bytes: &[u8],
140    ) -> Result<Self, anyhow::Error> {
141        let json = if let Some(layout) = layout {
142            // Try to convert Vec<u8> inputs into string
143            fn try_parse_string(layout: &MoveTypeLayout, bytes: &[u8]) -> Option<String> {
144                if let MoveTypeLayout::Vector(t) = layout
145                    && let MoveTypeLayout::U8 = **t
146                {
147                    return bcs::from_bytes::<String>(bytes).ok();
148                }
149                None
150            }
151            if let Some(s) = try_parse_string(layout, bytes) {
152                json!(s)
153            } else {
154                BoundedVisitor::deserialize_value(bytes, layout).map_or_else(
155                    |_| {
156                        // fallback to array[u8] if fail to convert to json.
157                        JsonValue::Array(
158                            bytes
159                                .iter()
160                                .map(|b| JsonValue::Number(Number::from(*b)))
161                                .collect(),
162                        )
163                    },
164                    |move_value| {
165                        move_value_to_json(&move_value).unwrap_or_else(|| {
166                            // fallback to array[u8] if fail to convert to json.
167                            JsonValue::Array(
168                                bytes
169                                    .iter()
170                                    .map(|b| JsonValue::Number(Number::from(*b)))
171                                    .collect(),
172                            )
173                        })
174                    },
175                )
176            }
177        } else {
178            json!(bytes)
179        };
180        SuiJsonValue::new(json)
181    }
182
183    pub fn to_json_value(&self) -> JsonValue {
184        self.0.clone()
185    }
186
187    pub fn to_sui_address(&self) -> anyhow::Result<SuiAddress> {
188        json_value_to_sui_address(&self.0)
189    }
190
191    fn handle_inner_struct_layout(
192        inner_vec: &[MoveFieldLayout],
193        val: &JsonValue,
194        ty: &MoveTypeLayout,
195        s: &String,
196    ) -> Result<R::MoveValue, anyhow::Error> {
197        // delegate MoveValue construction to the case when JsonValue::String and
198        // MoveTypeLayout::Vector are handled to get an address (with 0x string
199        // prefix) or a vector of u8s (no prefix)
200        debug_assert!(matches!(val, JsonValue::String(_)));
201
202        if inner_vec.len() != 1 {
203            bail!(
204                "Cannot convert string arg {s} to {ty} which is expected \
205                 to be a struct with one field"
206            );
207        }
208
209        match &inner_vec[0].layout {
210            MoveTypeLayout::Vector(inner) => match **inner {
211                MoveTypeLayout::U8 => Ok(R::MoveValue::Struct(R::MoveStruct(vec![
212                    Self::to_move_value(val, &inner_vec[0].layout.clone())?,
213                ]))),
214                MoveTypeLayout::Address => Ok(R::MoveValue::Struct(R::MoveStruct(vec![
215                    Self::to_move_value(val, &MoveTypeLayout::Address)?,
216                ]))),
217                _ => bail!(
218                    "Cannot convert string arg {s} to {ty} \
219                             which is expected to be a struct \
220                             with one field of address or u8 vector type"
221                ),
222            },
223            MoveTypeLayout::Struct(struct_layout) if struct_layout.type_ == ID::type_() => {
224                Ok(R::MoveValue::Struct(R::MoveStruct(vec![
225                    Self::to_move_value(val, &inner_vec[0].layout.clone())?,
226                ])))
227            }
228            _ => bail!(
229                "Cannot convert string arg {s} to {ty} which is expected \
230                 to be a struct with one field of a vector type"
231            ),
232        }
233    }
234
235    pub fn to_move_value(
236        val: &JsonValue,
237        ty: &MoveTypeLayout,
238    ) -> Result<R::MoveValue, anyhow::Error> {
239        Ok(match (val, ty) {
240            // Bool to Bool is simple
241            (JsonValue::Bool(b), MoveTypeLayout::Bool) => R::MoveValue::Bool(*b),
242
243            // In constructor, we have already checked that the JSON number is unsigned int of at most U32
244            (JsonValue::Number(n), MoveTypeLayout::U8) => match n.as_u64() {
245                Some(x) => R::MoveValue::U8(u8::try_from(x)?),
246                None => return Err(anyhow!("{} is not a valid number. Only u8 allowed.", n)),
247            },
248            (JsonValue::Number(n), MoveTypeLayout::U16) => match n.as_u64() {
249                Some(x) => R::MoveValue::U16(u16::try_from(x)?),
250                None => return Err(anyhow!("{} is not a valid number. Only u16 allowed.", n)),
251            },
252            (JsonValue::Number(n), MoveTypeLayout::U32) => match n.as_u64() {
253                Some(x) => R::MoveValue::U32(u32::try_from(x)?),
254                None => return Err(anyhow!("{} is not a valid number. Only u32 allowed.", n)),
255            },
256
257            // u8, u16, u32, u64, u128, u256 can be encoded as String
258            (JsonValue::String(s), MoveTypeLayout::U8) => {
259                R::MoveValue::U8(u8::try_from(convert_string_to_u256(s.as_str())?)?)
260            }
261            (JsonValue::String(s), MoveTypeLayout::U16) => {
262                R::MoveValue::U16(u16::try_from(convert_string_to_u256(s.as_str())?)?)
263            }
264            (JsonValue::String(s), MoveTypeLayout::U32) => {
265                R::MoveValue::U32(u32::try_from(convert_string_to_u256(s.as_str())?)?)
266            }
267            (JsonValue::String(s), MoveTypeLayout::U64) => {
268                R::MoveValue::U64(u64::try_from(convert_string_to_u256(s.as_str())?)?)
269            }
270            (JsonValue::String(s), MoveTypeLayout::U128) => {
271                R::MoveValue::U128(u128::try_from(convert_string_to_u256(s.as_str())?)?)
272            }
273            (JsonValue::String(s), MoveTypeLayout::U256) => {
274                R::MoveValue::U256(convert_string_to_u256(s.as_str())?)
275            }
276            // For ascii and utf8 strings
277            (JsonValue::String(s), MoveTypeLayout::Struct(struct_layout))
278                if is_move_string_type(&struct_layout.type_) =>
279            {
280                R::MoveValue::Vector(s.as_bytes().iter().copied().map(R::MoveValue::U8).collect())
281            }
282            // For ID
283            (JsonValue::String(s), MoveTypeLayout::Struct(struct_layout))
284                if struct_layout.type_ == ID::type_() =>
285            {
286                if struct_layout.fields.len() != 1 {
287                    bail!(
288                        "Cannot convert string arg {s} to {} which is expected to be a struct with one field",
289                        struct_layout.type_
290                    );
291                };
292                let addr = SuiAddress::from_str(s)?;
293                R::MoveValue::Address(addr.into())
294            }
295            (JsonValue::Object(o), MoveTypeLayout::Struct(struct_layout)) => {
296                let mut field_values = vec![];
297                for layout in struct_layout.fields.iter() {
298                    let field = o
299                        .get(layout.name.as_str())
300                        .ok_or_else(|| anyhow!("Missing field {} for struct {ty}", layout.name))?;
301                    field_values.push(Self::to_move_value(field, &layout.layout)?);
302                }
303                R::MoveValue::Struct(R::MoveStruct(field_values))
304            }
305            // Unnest fields
306            (value, MoveTypeLayout::Struct(struct_layout)) if struct_layout.fields.len() == 1 => {
307                Self::to_move_value(value, &struct_layout.fields[0].layout)?
308            }
309            (JsonValue::String(s), MoveTypeLayout::Vector(t)) => {
310                match &**t {
311                    MoveTypeLayout::U8 => {
312                        // We can encode U8 Vector as string in 2 ways
313                        // 1. If it starts with 0x, we treat it as hex strings, where each pair is a
314                        //    byte
315                        // 2. If it does not start with 0x, we treat each character as an ASCII
316                        //    encoded byte
317                        // We have to support both for the convenience of the user. This is because
318                        // sometime we need Strings as arg Other times we need vec of hex bytes for
319                        // address. Issue is both Address and Strings are represented as Vec<u8> in
320                        // Move call
321                        let vec = if s.starts_with(HEX_PREFIX) {
322                            // If starts with 0x, treat as hex vector
323                            Hex::decode(s).map_err(|e| anyhow!(e))?
324                        } else {
325                            // Else raw bytes
326                            s.as_bytes().to_vec()
327                        };
328                        R::MoveValue::Vector(vec.iter().copied().map(R::MoveValue::U8).collect())
329                    }
330                    MoveTypeLayout::Struct(struct_layout) => {
331                        Self::handle_inner_struct_layout(&struct_layout.fields, val, ty, s)?
332                    }
333                    _ => bail!("Cannot convert string arg {s} to {ty}"),
334                }
335            }
336
337            // We have already checked that the array is homogeneous in the constructor
338            (JsonValue::Array(a), MoveTypeLayout::Vector(inner)) => {
339                // Recursively build an IntermediateValue array
340                R::MoveValue::Vector(
341                    a.iter()
342                        .map(|i| Self::to_move_value(i, inner))
343                        .collect::<Result<Vec<_>, _>>()?,
344                )
345            }
346
347            (v, MoveTypeLayout::Address) => {
348                let addr = json_value_to_sui_address(v)?;
349                R::MoveValue::Address(addr.into())
350            }
351
352            _ => bail!("Unexpected arg {val:?} for expected type {ty:?}"),
353        })
354    }
355}
356
357impl Debug for SuiJsonValue {
358    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
359        write!(f, "{}", self.0)
360    }
361}
362
363fn json_value_to_sui_address(value: &JsonValue) -> anyhow::Result<SuiAddress> {
364    match value {
365        JsonValue::String(s) => {
366            let s = s.trim().to_lowercase();
367            if !s.starts_with(HEX_PREFIX) {
368                bail!("Address hex string must start with 0x.",);
369            }
370            Ok(SuiAddress::from_str(&s)?)
371        }
372        JsonValue::Array(bytes) => {
373            fn value_to_byte_array(v: &Vec<JsonValue>) -> Option<Vec<u8>> {
374                let mut bytes = vec![];
375                for b in v {
376                    let b = b.as_u64()?;
377                    if b <= u8::MAX as u64 {
378                        bytes.push(b as u8);
379                    } else {
380                        return None;
381                    }
382                }
383                Some(bytes)
384            }
385            let bytes = value_to_byte_array(bytes)
386                .ok_or_else(|| anyhow!("Invalid input: Cannot parse input into SuiAddress."))?;
387            Ok(SuiAddress::try_from(bytes)?)
388        }
389        v => bail!("Unexpected arg {v} for expected type address"),
390    }
391}
392
393fn move_value_to_json(move_value: &MoveValue) -> Option<JsonValue> {
394    Some(match move_value {
395        MoveValue::Vector(values) => JsonValue::Array(
396            values
397                .iter()
398                .map(move_value_to_json)
399                .collect::<Option<_>>()?,
400        ),
401        MoveValue::Bool(v) => json!(v),
402        MoveValue::Signer(v) | MoveValue::Address(v) => json!(SuiAddress::from(*v).to_string()),
403        MoveValue::U8(v) => json!(v),
404        MoveValue::U64(v) => json!(v.to_string()),
405        MoveValue::U128(v) => json!(v.to_string()),
406        MoveValue::U16(v) => json!(v),
407        MoveValue::U32(v) => json!(v),
408        MoveValue::U256(v) => json!(v.to_string()),
409        MoveValue::Struct(move_struct) => match move_struct {
410            MoveStruct { fields, type_ } if is_move_string_type(type_) => {
411                // ascii::string and utf8::string has a single bytes field.
412                let (_, v) = fields.first()?;
413                let string: String = bcs::from_bytes(&v.simple_serialize()?).ok()?;
414                json!(string)
415            }
416            MoveStruct { fields, type_ } if is_move_option_type(type_) => {
417                // option has a single vec field.
418                let (_, v) = fields.first()?;
419                if let MoveValue::Vector(v) = v {
420                    JsonValue::Array(v.iter().filter_map(move_value_to_json).collect::<Vec<_>>())
421                } else {
422                    return None;
423                }
424            }
425            MoveStruct { fields, type_ } if type_ == &ID::type_() => {
426                // option has a single vec field.
427                let (_, v) = fields.first()?;
428                if let MoveValue::Address(address) = v {
429                    json!(SuiAddress::from(*address))
430                } else {
431                    return None;
432                }
433            }
434            // We only care about values here, assuming struct type information is known at the client side.
435            MoveStruct { fields, .. } => {
436                let fields = fields
437                    .iter()
438                    .map(|(key, value)| (key, move_value_to_json(value)))
439                    .collect::<BTreeMap<_, _>>();
440                json!(fields)
441            }
442        },
443        // Don't return the type assuming type information is known at the client side.
444        MoveValue::Variant(MoveVariant {
445            type_: _,
446            tag: _,
447            variant_name,
448            fields,
449        }) => {
450            let fields = fields
451                .iter()
452                .map(|(key, value)| (key, move_value_to_json(value)))
453                .collect::<BTreeMap<_, _>>();
454            json!({
455                "variant": variant_name.to_string(),
456                "fields": fields,
457            })
458        }
459    })
460}
461
462fn is_move_string_type(tag: &StructTag) -> bool {
463    (tag.address == MOVE_STDLIB_ADDRESS
464        && tag.module.as_ident_str() == STD_UTF8_MODULE_NAME
465        && tag.name.as_ident_str() == STD_UTF8_STRUCT_NAME)
466        || (tag.address == MOVE_STDLIB_ADDRESS
467            && tag.module.as_ident_str() == STD_ASCII_MODULE_NAME
468            && tag.name.as_ident_str() == STD_ASCII_STRUCT_NAME)
469}
470fn is_move_option_type(tag: &StructTag) -> bool {
471    tag.address == MOVE_STDLIB_ADDRESS
472        && tag.module.as_ident_str() == STD_OPTION_MODULE_NAME
473        && tag.name.as_ident_str() == STD_OPTION_STRUCT_NAME
474}
475
476impl FromStr for SuiJsonValue {
477    type Err = anyhow::Error;
478    fn from_str(s: &str) -> Result<Self, anyhow::Error> {
479        fn try_escape_array(s: &str) -> JsonValue {
480            let s = s.trim();
481            if s.starts_with('[')
482                && s.ends_with(']')
483                && let Some(s) = s.strip_prefix('[').and_then(|s| s.strip_suffix(']'))
484            {
485                return JsonValue::Array(s.split(',').map(try_escape_array).collect());
486            }
487            json!(s)
488        }
489        // if serde_json fails, the failure usually cause by missing quote escapes, try parse array manually.
490        SuiJsonValue::new(serde_json::from_str(s).unwrap_or_else(|_| try_escape_array(s)))
491    }
492}
493
494#[derive(Eq, PartialEq, Debug, Clone, Hash)]
495enum ValidJsonType {
496    Bool,
497    Number,
498    String,
499    Array,
500    // Matches any type
501    Any,
502}
503
504/// Check via BFS
505/// The invariant is that all types at a given level must be the same or be empty, and all must be valid
506pub fn check_valid_homogeneous(val: &JsonValue) -> Result<(), SuiJsonValueError> {
507    let mut deq: VecDeque<&JsonValue> = VecDeque::new();
508    deq.push_back(val);
509    check_valid_homogeneous_rec(&mut deq)
510}
511
512/// Check via BFS
513/// The invariant is that all types at a given level must be the same or be empty
514fn check_valid_homogeneous_rec(curr_q: &mut VecDeque<&JsonValue>) -> Result<(), SuiJsonValueError> {
515    if curr_q.is_empty() {
516        // Nothing to do
517        return Ok(());
518    }
519    // Queue for the next level
520    let mut next_q = VecDeque::new();
521    // The types at this level must be the same
522    let mut level_type = ValidJsonType::Any;
523
524    // Process all in this queue/level
525    while let Some(v) = curr_q.pop_front() {
526        let curr = match v {
527            JsonValue::Bool(_) => ValidJsonType::Bool,
528            JsonValue::Number(x) if x.is_u64() => ValidJsonType::Number,
529            JsonValue::String(_) => ValidJsonType::String,
530            JsonValue::Array(w) => {
531                // Add to the next level
532                w.iter().for_each(|t| next_q.push_back(t));
533                ValidJsonType::Array
534            }
535            // Not valid
536            _ => {
537                return Err(SuiJsonValueError::new(
538                    v,
539                    SuiJsonValueErrorKind::ValueTypeNotAllowed,
540                ));
541            }
542        };
543
544        if level_type == ValidJsonType::Any {
545            // Update the level with the first found type
546            level_type = curr;
547        } else if level_type != curr {
548            // Mismatch in the level
549            return Err(SuiJsonValueError::new(
550                v,
551                SuiJsonValueErrorKind::ArrayNotHomogeneous,
552            ));
553        }
554    }
555    // Process the next level
556    check_valid_homogeneous_rec(&mut next_q)
557}
558
559/// Checks if a give SignatureToken represents a primitive type and, if so, returns MoveTypeLayout
560/// for this type (if available). The reason we need to return both information about whether a
561/// SignatureToken represents a primitive and an Option representing MoveTypeLayout is that there
562/// can be signature tokens that represent primitives but that do not have corresponding
563/// MoveTypeLayout (e.g., SignatureToken::DatatypeInstantiation).
564pub fn primitive_type(
565    view: &CompiledModule,
566    type_args: &[TypeTag],
567    param: &SignatureToken,
568) -> Option<MoveTypeLayout> {
569    Some(match param {
570        SignatureToken::Bool => MoveTypeLayout::Bool,
571        SignatureToken::U8 => MoveTypeLayout::U8,
572        SignatureToken::U16 => MoveTypeLayout::U16,
573        SignatureToken::U32 => MoveTypeLayout::U32,
574        SignatureToken::U64 => MoveTypeLayout::U64,
575        SignatureToken::U128 => MoveTypeLayout::U128,
576        SignatureToken::U256 => MoveTypeLayout::U256,
577        SignatureToken::Address => MoveTypeLayout::Address,
578        SignatureToken::Vector(inner) => {
579            MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, inner)?))
580        }
581        SignatureToken::Datatype(struct_handle_idx) => {
582            let resolved_struct = resolve_struct(view, *struct_handle_idx);
583            if resolved_struct == RESOLVED_ASCII_STR {
584                MoveTypeLayout::Struct(Box::new(move_ascii_str_layout()))
585            } else if resolved_struct == RESOLVED_UTF8_STR {
586                // both structs structs representing strings have one field - a vector of type u8
587                MoveTypeLayout::Struct(Box::new(move_utf8_str_layout()))
588            } else if resolved_struct == RESOLVED_SUI_ID {
589                MoveTypeLayout::Struct(Box::new(id::ID::layout()))
590            } else {
591                return None;
592            }
593        }
594        SignatureToken::DatatypeInstantiation(struct_inst) => {
595            let (idx, targs) = &**struct_inst;
596            let resolved_struct = resolve_struct(view, *idx);
597            // is option of a primitive
598            if resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 {
599                // there is no MoveLayout for this so the type is not a primitive.
600                MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, &targs[0])?))
601            } else {
602                return None;
603            }
604        }
605        SignatureToken::TypeParameter(idx) => {
606            layout_of_primitive_typetag(type_args.get(*idx as usize)?)?
607        }
608        SignatureToken::Signer
609        | SignatureToken::Reference(_)
610        | SignatureToken::MutableReference(_) => return None,
611    })
612}
613
614fn layout_of_primitive_typetag(tag: &TypeTag) -> Option<MoveTypeLayout> {
615    use MoveTypeLayout as MTL;
616    if !is_primitive_type_tag(tag) {
617        return None;
618    }
619
620    Some(match tag {
621        TypeTag::Bool => MTL::Bool,
622        TypeTag::U8 => MTL::U8,
623        TypeTag::U16 => MTL::U16,
624        TypeTag::U32 => MTL::U32,
625        TypeTag::U64 => MTL::U64,
626        TypeTag::U128 => MTL::U128,
627        TypeTag::U256 => MTL::U256,
628        TypeTag::Address => MTL::Address,
629        TypeTag::Signer => return None,
630        TypeTag::Vector(tag) => MTL::Vector(Box::new(layout_of_primitive_typetag(tag)?)),
631        TypeTag::Struct(stag) => {
632            let StructTag {
633                address,
634                module,
635                name,
636                type_params: type_args,
637            } = &**stag;
638            let resolved_struct = (address, module.as_ident_str(), name.as_ident_str());
639            // is id or..
640            if resolved_struct == RESOLVED_SUI_ID {
641                MTL::Struct(Box::new(id::ID::layout()))
642            } else if resolved_struct == RESOLVED_ASCII_STR {
643                MTL::Struct(Box::new(move_ascii_str_layout()))
644            } else if resolved_struct == RESOLVED_UTF8_STR {
645                MTL::Struct(Box::new(move_utf8_str_layout()))
646            } else if resolved_struct == RESOLVED_STD_OPTION // is option of a primitive
647                && type_args.len() == 1
648                && is_primitive_type_tag(&type_args[0])
649            {
650                MTL::Vector(Box::new(
651                    layout_of_primitive_typetag(&type_args[0]).unwrap(),
652                ))
653            } else {
654                return None;
655            }
656        }
657    })
658}
659
660fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result<ObjectID, anyhow::Error> {
661    // Every elem has to be a string convertible to a ObjectID
662    match arg {
663        JsonValue::String(s) => {
664            let s = s.trim().to_lowercase();
665            if !s.starts_with(HEX_PREFIX) {
666                bail!("ObjectID hex string must start with 0x.",);
667            }
668            Ok(ObjectID::from_hex_literal(&s)?)
669        }
670        _ => bail!(
671            "Unable to parse arg {:?} as ObjectID at pos {}. Expected {:?}-byte hex string \
672                prefixed with 0x.",
673            arg,
674            idx,
675            ObjectID::LENGTH,
676        ),
677    }
678}
679
680fn resolve_object_vec_arg(idx: usize, arg: &SuiJsonValue) -> Result<Vec<ObjectID>, anyhow::Error> {
681    // Every elem has to be a string convertible to a ObjectID
682    match arg.to_json_value() {
683        JsonValue::Array(a) => {
684            let mut object_ids = vec![];
685            for id in a {
686                object_ids.push(resolve_object_arg(idx, &id)?);
687            }
688            Ok(object_ids)
689        }
690        JsonValue::String(s) if s.starts_with('[') && s.ends_with(']') => {
691            // Due to how escaping of square bracket works, we may be dealing with a JSON string
692            // representing a JSON array rather than with the array itself ("[0x42,0x7]" rather than
693            // [0x42,0x7]).
694            let mut object_ids = vec![];
695            for tok in s[1..s.len() - 1].split(',') {
696                let id = JsonValue::String(tok.to_string());
697                object_ids.push(resolve_object_arg(idx, &id)?);
698            }
699            Ok(object_ids)
700        }
701        _ => bail!(
702            "Unable to parse arg {:?} as vector of ObjectIDs at pos {}. \
703             Expected a vector of {:?}-byte hex strings prefixed with 0x.\n\
704             Consider escaping your curly braces with a backslash (as in \\[0x42,0x7\\]) \
705             or enclosing the whole vector in single quotes (as in '[0x42,0x7]')",
706            arg.to_json_value(),
707            idx,
708            ObjectID::LENGTH,
709        ),
710    }
711}
712
713fn resolve_call_arg(
714    view: &CompiledModule,
715    type_args: &[TypeTag],
716    idx: usize,
717    arg: &SuiJsonValue,
718    param: &SignatureToken,
719) -> Result<ResolvedCallArg, anyhow::Error> {
720    if let Some(layout) = primitive_type(view, type_args, param) {
721        return Ok(ResolvedCallArg::Pure(arg.to_bcs_bytes(&layout).map_err(
722            |e| {
723                anyhow!(
724                    "Could not serialize argument of type {:?} at {} into {}. Got error: {:?}",
725                    param,
726                    idx,
727                    layout,
728                    e
729                )
730            },
731        )?));
732    }
733
734    // in terms of non-primitives we only currently support objects and "flat" (depth == 1) vectors
735    // of objects (but not, for example, vectors of references)
736    match param {
737        SignatureToken::Datatype(_)
738        | SignatureToken::DatatypeInstantiation(_)
739        | SignatureToken::TypeParameter(_)
740        | SignatureToken::Reference(_)
741        | SignatureToken::MutableReference(_) => Ok(ResolvedCallArg::Object(resolve_object_arg(
742            idx,
743            &arg.to_json_value(),
744        )?)),
745        SignatureToken::Vector(inner) => match &**inner {
746            SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
747                Ok(ResolvedCallArg::ObjVec(resolve_object_vec_arg(idx, arg)?))
748            }
749            _ => {
750                bail!(
751                    "Unexpected non-primitive vector arg {:?} at {} with value {:?}",
752                    param,
753                    idx,
754                    arg
755                );
756            }
757        },
758        _ => bail!(
759            "Unexpected non-primitive arg {:?} at {} with value {:?}",
760            param,
761            idx,
762            arg
763        ),
764    }
765}
766
767pub fn is_receiving_argument(view: &CompiledModule, arg_type: &SignatureToken) -> bool {
768    use SignatureToken as ST;
769
770    // Progress down into references to determine if the underlying type is a receiving
771    // type or not.
772    let mut token = arg_type;
773    while let ST::Reference(inner) | ST::MutableReference(inner) = token {
774        token = inner;
775    }
776
777    matches!(
778        token,
779        ST::DatatypeInstantiation(inst) if resolve_struct(view, inst.0) == RESOLVED_RECEIVING_STRUCT && inst.1.len() == 1
780    )
781}
782
783fn resolve_call_args(
784    view: &CompiledModule,
785    type_args: &[TypeTag],
786    json_args: &[SuiJsonValue],
787    parameter_types: &[SignatureToken],
788) -> Result<Vec<ResolvedCallArg>, anyhow::Error> {
789    json_args
790        .iter()
791        .zip(parameter_types)
792        .enumerate()
793        .map(|(idx, (arg, param))| resolve_call_arg(view, type_args, idx, arg, param))
794        .collect()
795}
796
797/// Resolve the JSON args of a function into the expected formats to make them usable by Move call
798/// This is because we have special types which we need to specify in other formats
799pub fn resolve_move_function_args(
800    package: &MovePackage,
801    module_ident: Identifier,
802    function: Identifier,
803    type_args: &[TypeTag],
804    combined_args_json: Vec<SuiJsonValue>,
805) -> Result<Vec<(ResolvedCallArg, SignatureToken)>, anyhow::Error> {
806    // Extract the expected function signature
807    let module = package.deserialize_module(&module_ident, &BinaryConfig::standard())?;
808    let function_str = function.as_ident_str();
809    let fdef = module
810        .function_defs
811        .iter()
812        .find(|fdef| {
813            module.identifier_at(module.function_handle_at(fdef.function).name) == function_str
814        })
815        .ok_or_else(|| {
816            anyhow!(
817                "Could not resolve function {} in module {}",
818                function,
819                module_ident
820            )
821        })?;
822    let function_signature = module.function_handle_at(fdef.function);
823    let parameters = &module.signature_at(function_signature.parameters).0;
824
825    // Lengths have to match, less one, due to TxContext
826    let expected_len = match parameters.last() {
827        Some(param) if TxContext::kind(&module, param) != TxContextKind::None => {
828            parameters.len() - 1
829        }
830        _ => parameters.len(),
831    };
832    if combined_args_json.len() != expected_len {
833        bail!(
834            "Expected {} args, found {}",
835            expected_len,
836            combined_args_json.len()
837        );
838    }
839    // Check that the args are valid and convert to the correct format
840    let call_args = resolve_call_args(&module, type_args, &combined_args_json, parameters)?;
841    let tupled_call_args = call_args
842        .into_iter()
843        .zip(parameters.iter())
844        .map(|(arg, expected_type)| (arg, expected_type.clone()))
845        .collect::<Vec<_>>();
846    Ok(tupled_call_args)
847}
848
849fn convert_string_to_u256(s: &str) -> Result<U256, anyhow::Error> {
850    // Try as normal number
851    if let Ok(v) = s.parse::<U256>() {
852        return Ok(v);
853    }
854
855    // Check prefix
856    // For now only Hex supported
857    // TODO: add support for bin and octal?
858
859    let s = s.trim().to_lowercase();
860    if !s.starts_with(HEX_PREFIX) {
861        bail!("Unable to convert {s} to unsigned int.",);
862    }
863    U256::from_str_radix(s.trim_start_matches(HEX_PREFIX), 16).map_err(|e| e.into())
864}
865
866#[macro_export]
867macro_rules! call_args {
868        ($($value:expr),*) => {
869        Ok::<_, anyhow::Error>(vec![$(sui_json::call_arg!($value)?,)*])
870    };
871    }
872
873#[macro_export]
874macro_rules! call_arg {
875    ($value:expr) => {{
876        use sui_json::SuiJsonValue;
877        trait SuiJsonArg {
878            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue>;
879        }
880        // TODO: anyway to condense this?
881        impl SuiJsonArg for &str {
882            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
883                SuiJsonValue::from_str(self)
884            }
885        }
886        impl SuiJsonArg for String {
887            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
888                SuiJsonValue::from_str(&self)
889            }
890        }
891        impl SuiJsonArg for sui_types::base_types::ObjectID {
892            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
893                SuiJsonValue::from_str(&self.to_string())
894            }
895        }
896        impl SuiJsonArg for sui_types::base_types::SuiAddress {
897            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
898                SuiJsonValue::from_str(&self.to_string())
899            }
900        }
901        impl SuiJsonArg for u64 {
902            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
903                SuiJsonValue::from_bcs_bytes(
904                    Some(&sui_json::MoveTypeLayout::U64),
905                    &bcs::to_bytes(self)?,
906                )
907            }
908        }
909        impl SuiJsonArg for Vec<u8> {
910            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
911                SuiJsonValue::from_bcs_bytes(None, &self)
912            }
913        }
914        impl SuiJsonArg for &[u8] {
915            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
916                SuiJsonValue::from_bcs_bytes(None, self)
917            }
918        }
919        $value.to_sui_json()
920    }};
921}
922
923#[macro_export]
924macro_rules! type_args {
925    ($($value:expr), *) => {{
926        use sui_json_rpc_types::SuiTypeTag;
927        use sui_types::TypeTag;
928        trait SuiJsonTypeArg {
929            fn to_sui_json(&self) -> anyhow::Result<SuiTypeTag>;
930        }
931        impl <T: core::fmt::Display> SuiJsonTypeArg for T {
932            fn to_sui_json(&self) -> anyhow::Result<SuiTypeTag> {
933                Ok(sui_types::parse_sui_type_tag(&self.to_string())?.into())
934            }
935        }
936        Ok::<_, anyhow::Error>(vec![$($value.to_sui_json()?,)*])
937    }};
938    }