Skip to main content

iota_json/
lib.rs

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