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
579        SignatureToken::Vector(inner) => {
580            MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, inner)?))
581        }
582
583        SignatureToken::Datatype(struct_handle_idx) => {
584            let resolved_struct = resolve_struct(view, *struct_handle_idx);
585            if resolved_struct == RESOLVED_ASCII_STR {
586                MoveTypeLayout::Struct(Box::new(move_ascii_str_layout()))
587            } else if resolved_struct == RESOLVED_UTF8_STR {
588                // both structs structs representing strings have one field - a vector of type u8
589                MoveTypeLayout::Struct(Box::new(move_utf8_str_layout()))
590            } else if resolved_struct == RESOLVED_SUI_ID {
591                MoveTypeLayout::Struct(Box::new(id::ID::layout()))
592            } else {
593                return None;
594            }
595        }
596
597        SignatureToken::DatatypeInstantiation(struct_inst) => {
598            let (idx, targs) = &**struct_inst;
599            let resolved_struct = resolve_struct(view, *idx);
600            // is option of a primitive
601            if resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 {
602                // there is no MoveLayout for this so the type is not a primitive.
603                MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, &targs[0])?))
604            } else {
605                return None;
606            }
607        }
608
609        SignatureToken::TypeParameter(idx) => {
610            layout_of_primitive_typetag(type_args.get(*idx as usize)?)?
611        }
612
613        SignatureToken::Reference(sig) | SignatureToken::MutableReference(sig) => {
614            primitive_type(view, type_args, sig)?
615        }
616
617        SignatureToken::Signer => return None,
618    })
619}
620
621fn layout_of_primitive_typetag(tag: &TypeTag) -> Option<MoveTypeLayout> {
622    use MoveTypeLayout as MTL;
623    if !is_primitive_type_tag(tag) {
624        return None;
625    }
626
627    Some(match tag {
628        TypeTag::Bool => MTL::Bool,
629        TypeTag::U8 => MTL::U8,
630        TypeTag::U16 => MTL::U16,
631        TypeTag::U32 => MTL::U32,
632        TypeTag::U64 => MTL::U64,
633        TypeTag::U128 => MTL::U128,
634        TypeTag::U256 => MTL::U256,
635        TypeTag::Address => MTL::Address,
636        TypeTag::Signer => return None,
637        TypeTag::Vector(tag) => MTL::Vector(Box::new(layout_of_primitive_typetag(tag)?)),
638        TypeTag::Struct(stag) => {
639            let StructTag {
640                address,
641                module,
642                name,
643                type_params: type_args,
644            } = &**stag;
645            let resolved_struct = (address, module.as_ident_str(), name.as_ident_str());
646            // is id or..
647            if resolved_struct == RESOLVED_SUI_ID {
648                MTL::Struct(Box::new(id::ID::layout()))
649            } else if resolved_struct == RESOLVED_ASCII_STR {
650                MTL::Struct(Box::new(move_ascii_str_layout()))
651            } else if resolved_struct == RESOLVED_UTF8_STR {
652                MTL::Struct(Box::new(move_utf8_str_layout()))
653            } else if resolved_struct == RESOLVED_STD_OPTION // is option of a primitive
654                && type_args.len() == 1
655                && is_primitive_type_tag(&type_args[0])
656            {
657                MTL::Vector(Box::new(
658                    layout_of_primitive_typetag(&type_args[0]).unwrap(),
659                ))
660            } else {
661                return None;
662            }
663        }
664    })
665}
666
667fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result<ObjectID, anyhow::Error> {
668    // Every elem has to be a string convertible to a ObjectID
669    match arg {
670        JsonValue::String(s) => {
671            let s = s.trim().to_lowercase();
672            if !s.starts_with(HEX_PREFIX) {
673                bail!("ObjectID hex string must start with 0x.",);
674            }
675            Ok(ObjectID::from_hex_literal(&s)?)
676        }
677        _ => bail!(
678            "Unable to parse arg {:?} as ObjectID at pos {}. Expected {:?}-byte hex string \
679                prefixed with 0x.",
680            arg,
681            idx,
682            ObjectID::LENGTH,
683        ),
684    }
685}
686
687fn resolve_object_vec_arg(idx: usize, arg: &SuiJsonValue) -> Result<Vec<ObjectID>, anyhow::Error> {
688    // Every elem has to be a string convertible to a ObjectID
689    match arg.to_json_value() {
690        JsonValue::Array(a) => {
691            let mut object_ids = vec![];
692            for id in a {
693                object_ids.push(resolve_object_arg(idx, &id)?);
694            }
695            Ok(object_ids)
696        }
697        JsonValue::String(s) if s.starts_with('[') && s.ends_with(']') => {
698            // Due to how escaping of square bracket works, we may be dealing with a JSON string
699            // representing a JSON array rather than with the array itself ("[0x42,0x7]" rather than
700            // [0x42,0x7]).
701            let mut object_ids = vec![];
702            for tok in s[1..s.len() - 1].split(',') {
703                let id = JsonValue::String(tok.to_string());
704                object_ids.push(resolve_object_arg(idx, &id)?);
705            }
706            Ok(object_ids)
707        }
708        _ => bail!(
709            "Unable to parse arg {:?} as vector of ObjectIDs at pos {}. \
710             Expected a vector of {:?}-byte hex strings prefixed with 0x.\n\
711             Consider escaping your curly braces with a backslash (as in \\[0x42,0x7\\]) \
712             or enclosing the whole vector in single quotes (as in '[0x42,0x7]')",
713            arg.to_json_value(),
714            idx,
715            ObjectID::LENGTH,
716        ),
717    }
718}
719
720fn resolve_call_arg(
721    view: &CompiledModule,
722    type_args: &[TypeTag],
723    idx: usize,
724    arg: &SuiJsonValue,
725    param: &SignatureToken,
726) -> Result<ResolvedCallArg, anyhow::Error> {
727    if let Some(layout) = primitive_type(view, type_args, param) {
728        return Ok(ResolvedCallArg::Pure(arg.to_bcs_bytes(&layout).map_err(
729            |e| {
730                anyhow!(
731                    "Could not serialize argument of type {:?} at {} into {}. Got error: {:?}",
732                    param,
733                    idx,
734                    layout,
735                    e
736                )
737            },
738        )?));
739    }
740
741    // in terms of non-primitives we only currently support objects and "flat" (depth == 1) vectors
742    // of objects (but not, for example, vectors of references)
743    match param {
744        SignatureToken::Datatype(_)
745        | SignatureToken::DatatypeInstantiation(_)
746        | SignatureToken::TypeParameter(_)
747        | SignatureToken::Reference(_)
748        | SignatureToken::MutableReference(_) => Ok(ResolvedCallArg::Object(resolve_object_arg(
749            idx,
750            &arg.to_json_value(),
751        )?)),
752        SignatureToken::Vector(inner) => match &**inner {
753            SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
754                Ok(ResolvedCallArg::ObjVec(resolve_object_vec_arg(idx, arg)?))
755            }
756            _ => {
757                bail!(
758                    "Unexpected non-primitive vector arg {:?} at {} with value {:?}",
759                    param,
760                    idx,
761                    arg
762                );
763            }
764        },
765        _ => bail!(
766            "Unexpected non-primitive arg {:?} at {} with value {:?}",
767            param,
768            idx,
769            arg
770        ),
771    }
772}
773
774pub fn is_receiving_argument(view: &CompiledModule, arg_type: &SignatureToken) -> bool {
775    use SignatureToken as ST;
776
777    // Progress down into references to determine if the underlying type is a receiving
778    // type or not.
779    let mut token = arg_type;
780    while let ST::Reference(inner) | ST::MutableReference(inner) = token {
781        token = inner;
782    }
783
784    matches!(
785        token,
786        ST::DatatypeInstantiation(inst) if resolve_struct(view, inst.0) == RESOLVED_RECEIVING_STRUCT && inst.1.len() == 1
787    )
788}
789
790fn resolve_call_args(
791    view: &CompiledModule,
792    type_args: &[TypeTag],
793    json_args: &[SuiJsonValue],
794    parameter_types: &[SignatureToken],
795) -> Result<Vec<ResolvedCallArg>, anyhow::Error> {
796    json_args
797        .iter()
798        .zip(parameter_types)
799        .enumerate()
800        .map(|(idx, (arg, param))| resolve_call_arg(view, type_args, idx, arg, param))
801        .collect()
802}
803
804/// Resolve the JSON args of a function into the expected formats to make them usable by Move call
805/// This is because we have special types which we need to specify in other formats
806pub fn resolve_move_function_args(
807    package: &MovePackage,
808    module_ident: Identifier,
809    function: Identifier,
810    type_args: &[TypeTag],
811    combined_args_json: Vec<SuiJsonValue>,
812) -> Result<Vec<(ResolvedCallArg, SignatureToken)>, anyhow::Error> {
813    // Extract the expected function signature
814    let module = package.deserialize_module(&module_ident, &BinaryConfig::standard())?;
815    let function_str = function.as_ident_str();
816    let fdef = module
817        .function_defs
818        .iter()
819        .find(|fdef| {
820            module.identifier_at(module.function_handle_at(fdef.function).name) == function_str
821        })
822        .ok_or_else(|| {
823            anyhow!(
824                "Could not resolve function {} in module {}",
825                function,
826                module_ident
827            )
828        })?;
829    let function_signature = module.function_handle_at(fdef.function);
830    let parameters = &module.signature_at(function_signature.parameters).0;
831
832    // Lengths have to match, less one, due to TxContext
833    let expected_len = match parameters.last() {
834        Some(param) if TxContext::kind(&module, param) != TxContextKind::None => {
835            parameters.len() - 1
836        }
837        _ => parameters.len(),
838    };
839    if combined_args_json.len() != expected_len {
840        bail!(
841            "Expected {} args, found {}",
842            expected_len,
843            combined_args_json.len()
844        );
845    }
846    // Check that the args are valid and convert to the correct format
847    let call_args = resolve_call_args(&module, type_args, &combined_args_json, parameters)?;
848    let tupled_call_args = call_args
849        .into_iter()
850        .zip(parameters.iter())
851        .map(|(arg, expected_type)| (arg, expected_type.clone()))
852        .collect::<Vec<_>>();
853    Ok(tupled_call_args)
854}
855
856fn convert_string_to_u256(s: &str) -> Result<U256, anyhow::Error> {
857    // Try as normal number
858    if let Ok(v) = s.parse::<U256>() {
859        return Ok(v);
860    }
861
862    // Check prefix
863    // For now only Hex supported
864    // TODO: add support for bin and octal?
865
866    let s = s.trim().to_lowercase();
867    if !s.starts_with(HEX_PREFIX) {
868        bail!("Unable to convert {s} to unsigned int.",);
869    }
870    U256::from_str_radix(s.trim_start_matches(HEX_PREFIX), 16).map_err(|e| e.into())
871}
872
873#[macro_export]
874macro_rules! call_args {
875        ($($value:expr),*) => {
876        Ok::<_, anyhow::Error>(vec![$(sui_json::call_arg!($value)?,)*])
877    };
878    }
879
880#[macro_export]
881macro_rules! call_arg {
882    ($value:expr) => {{
883        use sui_json::SuiJsonValue;
884        trait SuiJsonArg {
885            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue>;
886        }
887        // TODO: anyway to condense this?
888        impl SuiJsonArg for &str {
889            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
890                SuiJsonValue::from_str(self)
891            }
892        }
893        impl SuiJsonArg for String {
894            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
895                SuiJsonValue::from_str(&self)
896            }
897        }
898        impl SuiJsonArg for sui_types::base_types::ObjectID {
899            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
900                SuiJsonValue::from_str(&self.to_string())
901            }
902        }
903        impl SuiJsonArg for sui_types::base_types::SuiAddress {
904            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
905                SuiJsonValue::from_str(&self.to_string())
906            }
907        }
908        impl SuiJsonArg for u64 {
909            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
910                SuiJsonValue::from_bcs_bytes(
911                    Some(&sui_json::MoveTypeLayout::U64),
912                    &bcs::to_bytes(self)?,
913                )
914            }
915        }
916        impl SuiJsonArg for Vec<u8> {
917            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
918                SuiJsonValue::from_bcs_bytes(None, &self)
919            }
920        }
921        impl SuiJsonArg for &[u8] {
922            fn to_sui_json(&self) -> anyhow::Result<SuiJsonValue> {
923                SuiJsonValue::from_bcs_bytes(None, self)
924            }
925        }
926        $value.to_sui_json()
927    }};
928}
929
930#[macro_export]
931macro_rules! type_args {
932    ($($value:expr), *) => {{
933        use sui_json_rpc_types::SuiTypeTag;
934        use sui_types::TypeTag;
935        trait SuiJsonTypeArg {
936            fn to_sui_json(&self) -> anyhow::Result<SuiTypeTag>;
937        }
938        impl <T: core::fmt::Display> SuiJsonTypeArg for T {
939            fn to_sui_json(&self) -> anyhow::Result<SuiTypeTag> {
940                Ok(sui_types::parse_sui_type_tag(&self.to_string())?.into())
941            }
942        }
943        Ok::<_, anyhow::Error>(vec![$($value.to_sui_json()?,)*])
944    }};
945    }