use std::{
collections::{BTreeMap, VecDeque},
fmt::{self, Debug, Formatter},
str::FromStr,
};
use anyhow::{anyhow, bail};
use fastcrypto::encoding::{Encoding, Hex};
use iota_types::{
MOVE_STDLIB_ADDRESS,
base_types::{
IotaAddress, ObjectID, RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR,
STD_ASCII_MODULE_NAME, STD_ASCII_STRUCT_NAME, STD_OPTION_MODULE_NAME,
STD_OPTION_STRUCT_NAME, STD_UTF8_MODULE_NAME, STD_UTF8_STRUCT_NAME, TxContext,
TxContextKind, is_primitive_type_tag,
},
id::{ID, RESOLVED_IOTA_ID},
move_package::MovePackage,
object::bounded_visitor::BoundedVisitor,
transfer::RESOLVED_RECEIVING_STRUCT,
};
use move_binary_format::{
CompiledModule, binary_config::BinaryConfig, file_format::SignatureToken,
};
use move_bytecode_utils::resolve_struct;
pub use move_core_types::annotated_value::MoveTypeLayout;
use move_core_types::{
account_address::AccountAddress,
annotated_value::{MoveFieldLayout, MoveStruct, MoveStructLayout, MoveValue, MoveVariant},
ident_str,
identifier::{IdentStr, Identifier},
language_storage::{StructTag, TypeTag},
runtime_value as R,
u256::U256,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Number, Value as JsonValue, json};
const HEX_PREFIX: &str = "0x";
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum IotaJsonValueErrorKind {
ValueTypeNotAllowed,
ArrayNotHomogeneous,
}
#[derive(Debug)]
pub struct IotaJsonValueError {
kind: IotaJsonValueErrorKind,
val: JsonValue,
}
impl IotaJsonValueError {
pub fn new(val: &JsonValue, kind: IotaJsonValueErrorKind) -> Self {
Self {
kind,
val: val.clone(),
}
}
}
impl std::error::Error for IotaJsonValueError {}
impl fmt::Display for IotaJsonValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err_str = match self.kind {
IotaJsonValueErrorKind::ValueTypeNotAllowed => {
format!("JSON value type {} not allowed.", self.val)
}
IotaJsonValueErrorKind::ArrayNotHomogeneous => {
format!("Array not homogeneous. Mismatched value: {}.", self.val)
}
};
write!(f, "{err_str}")
}
}
#[derive(Eq, PartialEq, Debug)]
pub enum ResolvedCallArg {
Object(ObjectID),
Pure(Vec<u8>),
ObjVec(Vec<ObjectID>),
}
#[derive(Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
pub struct IotaJsonValue(JsonValue);
impl IotaJsonValue {
pub fn new(json_value: JsonValue) -> Result<IotaJsonValue, anyhow::Error> {
Self::check_value(&json_value)?;
Ok(Self(json_value))
}
fn check_value(json_value: &JsonValue) -> Result<(), anyhow::Error> {
match json_value {
JsonValue::Bool(_) | JsonValue::String(_) => (),
JsonValue::Number(n) => {
if !n.is_u64() {
return Err(anyhow!(
"{n} not allowed. Number must be unsigned integer of at most u32"
));
}
}
JsonValue::Array(a) => {
check_valid_homogeneous(&JsonValue::Array(a.to_vec()))?
}
JsonValue::Object(v) => {
for (_, value) in v {
Self::check_value(value)?;
}
}
JsonValue::Null => bail!("Null not allowed."),
};
Ok(())
}
pub fn from_object_id(id: ObjectID) -> IotaJsonValue {
Self(JsonValue::String(id.to_hex_uncompressed()))
}
pub fn to_bcs_bytes(&self, ty: &MoveTypeLayout) -> Result<Vec<u8>, anyhow::Error> {
let move_value = Self::to_move_value(&self.0, ty)?;
R::MoveValue::simple_serialize(&move_value)
.ok_or_else(|| anyhow!("Unable to serialize {:?}. Expected {}", move_value, ty))
}
pub fn from_bcs_bytes(
layout: Option<&MoveTypeLayout>,
bytes: &[u8],
) -> Result<Self, anyhow::Error> {
let json = if let Some(layout) = layout {
fn try_parse_string(layout: &MoveTypeLayout, bytes: &[u8]) -> Option<String> {
if let MoveTypeLayout::Vector(t) = layout {
if let MoveTypeLayout::U8 = **t {
return bcs::from_bytes::<String>(bytes).ok();
}
}
None
}
if let Some(s) = try_parse_string(layout, bytes) {
json!(s)
} else {
let result = BoundedVisitor::deserialize_value(bytes, layout).map_or_else(
|_| {
JsonValue::Array(
bytes
.iter()
.map(|b| JsonValue::Number(Number::from(*b)))
.collect(),
)
},
|move_value| {
move_value_to_json(&move_value).unwrap_or_else(|| {
JsonValue::Array(
bytes
.iter()
.map(|b| JsonValue::Number(Number::from(*b)))
.collect(),
)
})
},
);
result
}
} else {
json!(bytes)
};
IotaJsonValue::new(json)
}
pub fn to_json_value(&self) -> JsonValue {
self.0.clone()
}
pub fn to_iota_address(&self) -> anyhow::Result<IotaAddress> {
json_value_to_iota_address(&self.0)
}
fn handle_inner_struct_layout(
inner_vec: &[MoveFieldLayout],
val: &JsonValue,
ty: &MoveTypeLayout,
s: &String,
) -> Result<R::MoveValue, anyhow::Error> {
debug_assert!(matches!(val, JsonValue::String(_)));
if inner_vec.len() != 1 {
bail!(
"Cannot convert string arg {s} to {ty} which is expected \
to be a struct with one field"
);
}
match &inner_vec[0].layout {
MoveTypeLayout::Vector(inner) => match **inner {
MoveTypeLayout::U8 => Ok(R::MoveValue::Struct(R::MoveStruct(vec![
Self::to_move_value(val, &inner_vec[0].layout.clone())?,
]))),
MoveTypeLayout::Address => Ok(R::MoveValue::Struct(R::MoveStruct(vec![
Self::to_move_value(val, &MoveTypeLayout::Address)?,
]))),
_ => bail!(
"Cannot convert string arg {s} to {ty} \
which is expected to be a struct \
with one field of address or u8 vector type"
),
},
MoveTypeLayout::Struct(MoveStructLayout { type_, .. }) if type_ == &ID::type_() => {
Ok(R::MoveValue::Struct(R::MoveStruct(vec![
Self::to_move_value(val, &inner_vec[0].layout.clone())?,
])))
}
_ => bail!(
"Cannot convert string arg {s} to {ty} which is expected \
to be a struct with one field of a vector type"
),
}
}
pub fn to_move_value(
val: &JsonValue,
ty: &MoveTypeLayout,
) -> Result<R::MoveValue, anyhow::Error> {
Ok(match (val, ty) {
(JsonValue::Bool(b), MoveTypeLayout::Bool) => R::MoveValue::Bool(*b),
(JsonValue::Number(n), MoveTypeLayout::U8) => match n.as_u64() {
Some(x) => R::MoveValue::U8(u8::try_from(x)?),
None => return Err(anyhow!("{} is not a valid number. Only u8 allowed.", n)),
},
(JsonValue::Number(n), MoveTypeLayout::U16) => match n.as_u64() {
Some(x) => R::MoveValue::U16(u16::try_from(x)?),
None => return Err(anyhow!("{} is not a valid number. Only u16 allowed.", n)),
},
(JsonValue::Number(n), MoveTypeLayout::U32) => match n.as_u64() {
Some(x) => R::MoveValue::U32(u32::try_from(x)?),
None => return Err(anyhow!("{} is not a valid number. Only u32 allowed.", n)),
},
(JsonValue::String(s), MoveTypeLayout::U8) => {
R::MoveValue::U8(u8::try_from(convert_string_to_u256(s.as_str())?)?)
}
(JsonValue::String(s), MoveTypeLayout::U16) => {
R::MoveValue::U16(u16::try_from(convert_string_to_u256(s.as_str())?)?)
}
(JsonValue::String(s), MoveTypeLayout::U32) => {
R::MoveValue::U32(u32::try_from(convert_string_to_u256(s.as_str())?)?)
}
(JsonValue::String(s), MoveTypeLayout::U64) => {
R::MoveValue::U64(u64::try_from(convert_string_to_u256(s.as_str())?)?)
}
(JsonValue::String(s), MoveTypeLayout::U128) => {
R::MoveValue::U128(u128::try_from(convert_string_to_u256(s.as_str())?)?)
}
(JsonValue::String(s), MoveTypeLayout::U256) => {
R::MoveValue::U256(convert_string_to_u256(s.as_str())?)
}
(
JsonValue::String(s),
MoveTypeLayout::Struct(MoveStructLayout { type_, fields: _ }),
) if is_move_string_type(type_) => {
R::MoveValue::Vector(s.as_bytes().iter().copied().map(R::MoveValue::U8).collect())
}
(JsonValue::String(s), MoveTypeLayout::Struct(MoveStructLayout { type_, fields }))
if type_ == &ID::type_() =>
{
if fields.len() != 1 {
bail!(
"Cannot convert string arg {s} to {type_} which is expected to be a struct with one field"
);
};
let addr = IotaAddress::from_str(s)?;
R::MoveValue::Address(addr.into())
}
(JsonValue::Object(o), MoveTypeLayout::Struct(MoveStructLayout { fields, .. })) => {
let mut field_values = vec![];
for layout in fields {
let field = o
.get(layout.name.as_str())
.ok_or_else(|| anyhow!("Missing field {} for struct {ty}", layout.name))?;
field_values.push(Self::to_move_value(field, &layout.layout)?);
}
R::MoveValue::Struct(R::MoveStruct(field_values))
}
(value, MoveTypeLayout::Struct(MoveStructLayout { fields, .. }))
if fields.len() == 1 =>
{
Self::to_move_value(value, &fields[0].layout)?
}
(JsonValue::String(s), MoveTypeLayout::Vector(t)) => {
match &**t {
MoveTypeLayout::U8 => {
let vec = if s.starts_with(HEX_PREFIX) {
Hex::decode(s).map_err(|e| anyhow!(e))?
} else {
s.as_bytes().to_vec()
};
R::MoveValue::Vector(vec.iter().copied().map(R::MoveValue::U8).collect())
}
MoveTypeLayout::Struct(MoveStructLayout { fields: inner, .. }) => {
Self::handle_inner_struct_layout(inner, val, ty, s)?
}
_ => bail!("Cannot convert string arg {s} to {ty}"),
}
}
(JsonValue::Array(a), MoveTypeLayout::Vector(inner)) => {
R::MoveValue::Vector(
a.iter()
.map(|i| Self::to_move_value(i, inner))
.collect::<Result<Vec<_>, _>>()?,
)
}
(v, MoveTypeLayout::Address) => {
let addr = json_value_to_iota_address(v)?;
R::MoveValue::Address(addr.into())
}
_ => bail!("Unexpected arg {val:?} for expected type {ty:?}"),
})
}
}
impl Debug for IotaJsonValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
fn json_value_to_iota_address(value: &JsonValue) -> anyhow::Result<IotaAddress> {
match value {
JsonValue::String(s) => {
let s = s.trim().to_lowercase();
if !s.starts_with(HEX_PREFIX) {
bail!("Address hex string must start with 0x.",);
}
Ok(IotaAddress::from_str(&s)?)
}
JsonValue::Array(bytes) => {
fn value_to_byte_array(v: &Vec<JsonValue>) -> Option<Vec<u8>> {
let mut bytes = vec![];
for b in v {
let b = b.as_u64()?;
if b <= u8::MAX as u64 {
bytes.push(b as u8);
} else {
return None;
}
}
Some(bytes)
}
let bytes = value_to_byte_array(bytes)
.ok_or_else(|| anyhow!("Invalid input: Cannot parse input into IotaAddress."))?;
Ok(IotaAddress::try_from(bytes)?)
}
v => bail!("Unexpected arg {v} for expected type address"),
}
}
fn move_value_to_json(move_value: &MoveValue) -> Option<JsonValue> {
Some(match move_value {
MoveValue::Vector(values) => JsonValue::Array(
values
.iter()
.map(move_value_to_json)
.collect::<Option<_>>()?,
),
MoveValue::Bool(v) => json!(v),
MoveValue::Signer(v) | MoveValue::Address(v) => json!(IotaAddress::from(*v).to_string()),
MoveValue::U8(v) => json!(v),
MoveValue::U64(v) => json!(v.to_string()),
MoveValue::U128(v) => json!(v.to_string()),
MoveValue::U16(v) => json!(v),
MoveValue::U32(v) => json!(v),
MoveValue::U256(v) => json!(v.to_string()),
MoveValue::Struct(move_struct) => match move_struct {
MoveStruct { fields, type_ } if is_move_string_type(type_) => {
let (_, v) = fields.first()?;
let string: String = bcs::from_bytes(&v.simple_serialize()?).ok()?;
json!(string)
}
MoveStruct { fields, type_ } if is_move_option_type(type_) => {
let (_, v) = fields.first()?;
if let MoveValue::Vector(v) = v {
JsonValue::Array(v.iter().filter_map(move_value_to_json).collect::<Vec<_>>())
} else {
return None;
}
}
MoveStruct { fields, type_ } if type_ == &ID::type_() => {
let (_, v) = fields.first()?;
if let MoveValue::Address(address) = v {
json!(IotaAddress::from(*address))
} else {
return None;
}
}
MoveStruct { fields, .. } => {
let fields = fields
.iter()
.map(|(key, value)| (key, move_value_to_json(value)))
.collect::<BTreeMap<_, _>>();
json!(fields)
}
},
MoveValue::Variant(MoveVariant {
type_: _,
tag: _,
variant_name,
fields,
}) => {
let fields = fields
.iter()
.map(|(key, value)| (key, move_value_to_json(value)))
.collect::<BTreeMap<_, _>>();
json!({
"variant": variant_name.to_string(),
"fields": fields,
})
}
})
}
fn is_move_string_type(tag: &StructTag) -> bool {
(tag.address == MOVE_STDLIB_ADDRESS
&& tag.module.as_ident_str() == STD_UTF8_MODULE_NAME
&& tag.name.as_ident_str() == STD_UTF8_STRUCT_NAME)
|| (tag.address == MOVE_STDLIB_ADDRESS
&& tag.module.as_ident_str() == STD_ASCII_MODULE_NAME
&& tag.name.as_ident_str() == STD_ASCII_STRUCT_NAME)
}
fn is_move_option_type(tag: &StructTag) -> bool {
tag.address == MOVE_STDLIB_ADDRESS
&& tag.module.as_ident_str() == STD_OPTION_MODULE_NAME
&& tag.name.as_ident_str() == STD_OPTION_STRUCT_NAME
}
impl FromStr for IotaJsonValue {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, anyhow::Error> {
fn try_escape_array(s: &str) -> JsonValue {
let s = s.trim();
if s.starts_with('[') && s.ends_with(']') {
if let Some(s) = s.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
return JsonValue::Array(s.split(',').map(try_escape_array).collect());
}
}
json!(s)
}
IotaJsonValue::new(serde_json::from_str(s).unwrap_or_else(|_| try_escape_array(s)))
}
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
enum ValidJsonType {
Bool,
Number,
String,
Array,
Any,
}
pub fn check_valid_homogeneous(val: &JsonValue) -> Result<(), IotaJsonValueError> {
let mut deq: VecDeque<&JsonValue> = VecDeque::new();
deq.push_back(val);
check_valid_homogeneous_rec(&mut deq)
}
fn check_valid_homogeneous_rec(
curr_q: &mut VecDeque<&JsonValue>,
) -> Result<(), IotaJsonValueError> {
if curr_q.is_empty() {
return Ok(());
}
let mut next_q = VecDeque::new();
let mut level_type = ValidJsonType::Any;
while let Some(v) = curr_q.pop_front() {
let curr = match v {
JsonValue::Bool(_) => ValidJsonType::Bool,
JsonValue::Number(x) if x.is_u64() => ValidJsonType::Number,
JsonValue::String(_) => ValidJsonType::String,
JsonValue::Array(w) => {
w.iter().for_each(|t| next_q.push_back(t));
ValidJsonType::Array
}
_ => {
return Err(IotaJsonValueError::new(
v,
IotaJsonValueErrorKind::ValueTypeNotAllowed,
));
}
};
if level_type == ValidJsonType::Any {
level_type = curr;
} else if level_type != curr {
return Err(IotaJsonValueError::new(
v,
IotaJsonValueErrorKind::ArrayNotHomogeneous,
));
}
}
check_valid_homogeneous_rec(&mut next_q)
}
pub fn primitive_type(
view: &CompiledModule,
type_args: &[TypeTag],
param: &SignatureToken,
) -> (bool, Option<MoveTypeLayout>) {
match param {
SignatureToken::Bool => (true, Some(MoveTypeLayout::Bool)),
SignatureToken::U8 => (true, Some(MoveTypeLayout::U8)),
SignatureToken::U16 => (true, Some(MoveTypeLayout::U16)),
SignatureToken::U32 => (true, Some(MoveTypeLayout::U32)),
SignatureToken::U64 => (true, Some(MoveTypeLayout::U64)),
SignatureToken::U128 => (true, Some(MoveTypeLayout::U128)),
SignatureToken::U256 => (true, Some(MoveTypeLayout::U256)),
SignatureToken::Address => (true, Some(MoveTypeLayout::Address)),
SignatureToken::Vector(inner) => {
let (is_primitive, inner_layout_opt) = primitive_type(view, type_args, inner);
match inner_layout_opt {
Some(inner_layout) => (
is_primitive,
Some(MoveTypeLayout::Vector(Box::new(inner_layout))),
),
None => (is_primitive, None),
}
}
SignatureToken::Datatype(struct_handle_idx) => {
let resolved_struct = resolve_struct(view, *struct_handle_idx);
if resolved_struct == RESOLVED_ASCII_STR {
(
true,
Some(MoveTypeLayout::Struct(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_ASCII_STR),
fields: vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
)],
})),
)
} else if resolved_struct == RESOLVED_UTF8_STR {
(
true,
Some(MoveTypeLayout::Struct(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_UTF8_STR),
fields: vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
)],
})),
)
} else if resolved_struct == RESOLVED_IOTA_ID {
(
true,
Some(MoveTypeLayout::Struct(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_IOTA_ID),
fields: vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Address,
)],
})),
)
} else {
(false, None)
}
}
SignatureToken::DatatypeInstantiation(struct_inst) => {
let (idx, targs) = &**struct_inst;
let resolved_struct = resolve_struct(view, *idx);
if resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 {
let (is_primitive, inner_layout) = primitive_type(view, type_args, &targs[0]);
let layout =
inner_layout.map(|inner_layout| MoveTypeLayout::Vector(Box::new(inner_layout)));
(is_primitive, layout)
} else {
(false, None)
}
}
SignatureToken::TypeParameter(idx) => (
type_args
.get(*idx as usize)
.map(is_primitive_type_tag)
.unwrap_or(false),
None,
),
SignatureToken::Signer
| SignatureToken::Reference(_)
| SignatureToken::MutableReference(_) => (false, None),
}
}
fn resolved_to_struct(resolved_type: (&AccountAddress, &IdentStr, &IdentStr)) -> StructTag {
StructTag {
address: *resolved_type.0,
module: resolved_type.1.into(),
name: resolved_type.2.into(),
type_params: vec![],
}
}
fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result<ObjectID, anyhow::Error> {
match arg {
JsonValue::String(s) => {
let s = s.trim().to_lowercase();
if !s.starts_with(HEX_PREFIX) {
bail!("ObjectID hex string must start with 0x.",);
}
Ok(ObjectID::from_hex_literal(&s)?)
}
_ => bail!(
"Unable to parse arg {:?} as ObjectID at pos {}. Expected {:?}-byte hex string \
prefixed with 0x.",
arg,
idx,
ObjectID::LENGTH,
),
}
}
fn resolve_object_vec_arg(idx: usize, arg: &IotaJsonValue) -> Result<Vec<ObjectID>, anyhow::Error> {
match arg.to_json_value() {
JsonValue::Array(a) => {
let mut object_ids = vec![];
for id in a {
object_ids.push(resolve_object_arg(idx, &id)?);
}
Ok(object_ids)
}
JsonValue::String(s) if s.starts_with('[') && s.ends_with(']') => {
let mut object_ids = vec![];
for tok in s[1..s.len() - 1].split(',') {
let id = JsonValue::String(tok.to_string());
object_ids.push(resolve_object_arg(idx, &id)?);
}
Ok(object_ids)
}
_ => bail!(
"Unable to parse arg {:?} as vector of ObjectIDs at pos {}. \
Expected a vector of {:?}-byte hex strings prefixed with 0x.\n\
Consider escaping your curly braces with a backslash (as in \\[0x42,0x7\\]) \
or enclosing the whole vector in single quotes (as in '[0x42,0x7]')",
arg.to_json_value(),
idx,
ObjectID::LENGTH,
),
}
}
fn resolve_call_arg(
view: &CompiledModule,
type_args: &[TypeTag],
idx: usize,
arg: &IotaJsonValue,
param: &SignatureToken,
) -> Result<ResolvedCallArg, anyhow::Error> {
let (is_primitive, layout_opt) = primitive_type(view, type_args, param);
if is_primitive {
match layout_opt {
Some(layout) => {
return Ok(ResolvedCallArg::Pure(arg.to_bcs_bytes(&layout).map_err(
|e| {
anyhow!(
"Could not serialize argument of type {:?} at {} into {}. Got error: {:?}",
param,
idx,
layout,
e
)
},
)?));
}
None => {
debug_assert!(
false,
"Should be unreachable. All primitive type function args \
should have a corresponding MoveLayout"
);
bail!(
"Could not serialize argument of type {:?} at {}",
param,
idx
);
}
}
}
match param {
SignatureToken::Datatype(_)
| SignatureToken::DatatypeInstantiation(_)
| SignatureToken::TypeParameter(_)
| SignatureToken::Reference(_)
| SignatureToken::MutableReference(_) => Ok(ResolvedCallArg::Object(resolve_object_arg(
idx,
&arg.to_json_value(),
)?)),
SignatureToken::Vector(inner) => match &**inner {
SignatureToken::Datatype(_) | SignatureToken::DatatypeInstantiation(_) => {
Ok(ResolvedCallArg::ObjVec(resolve_object_vec_arg(idx, arg)?))
}
_ => {
bail!(
"Unexpected non-primitive vector arg {:?} at {} with value {:?}",
param,
idx,
arg
);
}
},
_ => bail!(
"Unexpected non-primitive arg {:?} at {} with value {:?}",
param,
idx,
arg
),
}
}
pub fn is_receiving_argument(view: &CompiledModule, arg_type: &SignatureToken) -> bool {
use SignatureToken as ST;
let mut token = arg_type;
while let ST::Reference(inner) | ST::MutableReference(inner) = token {
token = inner;
}
matches!(
token,
ST::DatatypeInstantiation(inst) if resolve_struct(view, inst.0) == RESOLVED_RECEIVING_STRUCT && inst.1.len() == 1
)
}
fn resolve_call_args(
view: &CompiledModule,
type_args: &[TypeTag],
json_args: &[IotaJsonValue],
parameter_types: &[SignatureToken],
) -> Result<Vec<ResolvedCallArg>, anyhow::Error> {
json_args
.iter()
.zip(parameter_types)
.enumerate()
.map(|(idx, (arg, param))| resolve_call_arg(view, type_args, idx, arg, param))
.collect()
}
pub fn resolve_move_function_args(
package: &MovePackage,
module_ident: Identifier,
function: Identifier,
type_args: &[TypeTag],
combined_args_json: Vec<IotaJsonValue>,
) -> Result<Vec<(ResolvedCallArg, SignatureToken)>, anyhow::Error> {
let module = package.deserialize_module(&module_ident, &BinaryConfig::standard())?;
let function_str = function.as_ident_str();
let fdef = module
.function_defs
.iter()
.find(|fdef| {
module.identifier_at(module.function_handle_at(fdef.function).name) == function_str
})
.ok_or_else(|| {
anyhow!(
"Could not resolve function {} in module {}",
function,
module_ident
)
})?;
let function_signature = module.function_handle_at(fdef.function);
let parameters = &module.signature_at(function_signature.parameters).0;
let expected_len = match parameters.last() {
Some(param) if TxContext::kind(&module, param) != TxContextKind::None => {
parameters.len() - 1
}
_ => parameters.len(),
};
if combined_args_json.len() != expected_len {
bail!(
"Expected {} args, found {}",
expected_len,
combined_args_json.len()
);
}
let call_args = resolve_call_args(&module, type_args, &combined_args_json, parameters)?;
let tupled_call_args = call_args
.into_iter()
.zip(parameters.iter())
.map(|(arg, expected_type)| (arg, expected_type.clone()))
.collect::<Vec<_>>();
Ok(tupled_call_args)
}
fn convert_string_to_u256(s: &str) -> Result<U256, anyhow::Error> {
if let Ok(v) = s.parse::<U256>() {
return Ok(v);
}
let s = s.trim().to_lowercase();
if !s.starts_with(HEX_PREFIX) {
bail!("Unable to convert {s} to unsigned int.",);
}
U256::from_str_radix(s.trim_start_matches(HEX_PREFIX), 16).map_err(|e| e.into())
}
#[macro_export]
macro_rules! call_args {
($($value:expr),*) => {
Ok::<_, anyhow::Error>(vec![$(iota_json::call_arg!($value)?,)*])
};
}
#[macro_export]
macro_rules! call_arg {
($value:expr) => {{
use iota_json::IotaJsonValue;
trait IotaJsonArg {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue>;
}
impl IotaJsonArg for &str {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_str(self)
}
}
impl IotaJsonArg for String {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_str(&self)
}
}
impl IotaJsonArg for iota_types::base_types::ObjectID {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_str(&self.to_string())
}
}
impl IotaJsonArg for iota_types::base_types::IotaAddress {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_str(&self.to_string())
}
}
impl IotaJsonArg for u64 {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_bcs_bytes(
Some(&iota_json::MoveTypeLayout::U64),
&bcs::to_bytes(self)?,
)
}
}
impl IotaJsonArg for Vec<u8> {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_bcs_bytes(None, &self)
}
}
impl IotaJsonArg for &[u8] {
fn to_iota_json(&self) -> anyhow::Result<IotaJsonValue> {
IotaJsonValue::from_bcs_bytes(None, self)
}
}
$value.to_iota_json()
}};
}
#[macro_export]
macro_rules! type_args {
($($value:expr), *) => {{
use iota_json_rpc_types::IotaTypeTag;
use iota_types::TypeTag;
trait IotaJsonTypeArg {
fn to_iota_json(&self) -> anyhow::Result<IotaTypeTag>;
}
impl <T: core::fmt::Display> IotaJsonTypeArg for T {
fn to_iota_json(&self) -> anyhow::Result<IotaTypeTag> {
Ok(iota_types::parse_iota_type_tag(&self.to_string())?.into())
}
}
Ok::<_, anyhow::Error>(vec![$($value.to_iota_json()?,)*])
}};
}