Skip to content

Commit

Permalink
feat(CLI): display complex return results better (#43)
Browse files Browse the repository at this point in the history
* (WIP) add a converter function to convert move value to json object

* fmt

* refactor the code
  • Loading branch information
Poytr1 authored Dec 8, 2022
1 parent b311d4a commit e34f71f
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 15 deletions.
197 changes: 197 additions & 0 deletions src/converter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use crate::helper::get_module;
use crate::types::Network;
use anyhow::{anyhow, Result};
use aptos_sdk::rest_client::aptos_api_types::MoveType;
use aptos_sdk::rest_client::Client;
use move_core_types::account_address::AccountAddress;
use move_core_types::identifier::Identifier;
use move_core_types::language_storage::{ModuleId, TypeTag};
use move_core_types::value::{MoveStruct, MoveValue};
use serde_json::{json, Map, Value};
use std::borrow::Borrow;
use std::str::FromStr;

pub fn move_value_to_json(val: MoveValue) -> Value {
match val {
MoveValue::U8(n) => serde_json::to_value(n).unwrap(),
MoveValue::U64(n) => serde_json::to_value(n).unwrap(),
MoveValue::U128(n) => serde_json::to_value(n.to_string()).unwrap(),
MoveValue::Bool(b) => serde_json::to_value(b).unwrap(),
MoveValue::Address(add) => serde_json::to_value(add).unwrap(),
MoveValue::Vector(vals) => {
// If this is a vector<u8>, convert it to hex string
if is_non_empty_vector_u8(&vals) {
let bytes = vec_to_vec_u8(vals).unwrap();
serde_json::to_value(format!("0x{}", hex::encode(&bytes))).unwrap()
} else {
Value::Array(vals.into_iter().map(|v| move_value_to_json(v)).collect())
}
}
MoveValue::Struct(move_struct) => match move_struct {
MoveStruct::Runtime(fields) => {
Value::Array(fields.into_iter().map(|v| move_value_to_json(v)).collect())
}
MoveStruct::WithFields(fields) => struct_fields_to_json(fields),
MoveStruct::WithTypes { type_, fields } => struct_fields_to_json(fields),
},
MoveValue::Signer(add) => serde_json::to_value(add).unwrap(),
}
}

pub fn annotate_value(
val: MoveValue,
t: &MoveType,
client: Client,
network: Network,
cache_folder: String,
) -> MoveValue {
let mut annotated_value = val;
match t {
MoveType::Struct(struct_tag) => {
let module = ModuleId::new(
AccountAddress::from_bytes(struct_tag.address.inner().into_bytes()).unwrap(),
Identifier::from_str(struct_tag.module.as_str()).unwrap(),
);
let (_, abi) = get_module(
client.clone(),
&module,
format!("{}", network),
cache_folder.clone(),
)
.unwrap();

let fields_found = if let Some(ms) = abi
.unwrap()
.structs
.into_iter()
.find(|s| s.name.to_string() == struct_tag.name.to_string())
{
Some(ms.fields)
} else {
None
};

annotated_value = match annotated_value {
MoveValue::Struct(MoveStruct::Runtime(struct_vals)) => {
if let Some(fields) = fields_found {
let mut fields_iter = fields.into_iter();
MoveValue::Struct(MoveStruct::WithFields(
struct_vals
.into_iter()
.map(|v| {
let field = fields_iter.next().unwrap();
let id =
Identifier::from_str(field.name.0.into_string().as_str())
.unwrap();
let inner_tp: MoveType = field.typ;
(
id,
annotate_value(
v,
&inner_tp,
client.clone(),
network.clone(),
cache_folder.clone(),
),
)
})
.collect(),
))
} else {
MoveValue::Struct(MoveStruct::Runtime(struct_vals))
}
}
_ => annotated_value,
}
}
MoveType::Vector { items } => match items.borrow() {
// Vector of Struct
MoveType::Struct(_) => {
// value must be vector of struct
annotated_value = match annotated_value {
MoveValue::Vector(inner_vals) => MoveValue::Vector(
inner_vals
.into_iter()
.map(|v| {
annotate_value(
v,
items.borrow(),
client.clone(),
network.clone(),
cache_folder.clone(),
)
})
.collect(),
),
_ => panic!("Expect vector value here"),
};
}
_ => {}
},
_ => {}
}
annotated_value
}

fn struct_fields_to_json(fields: Vec<(Identifier, MoveValue)>) -> Value {
let mut iter = fields.into_iter();
let mut map = Map::new();
while let Some((field_name, field_value)) = iter.next() {
map.insert(field_name.into_string(), move_value_to_json(field_value));
}
Value::Object(map)
}

fn is_non_empty_vector_u8(vec: &Vec<MoveValue>) -> bool {
if vec.is_empty() {
false
} else {
matches!(vec.last().unwrap(), MoveValue::U8(_))
}
}

/// Converts the `Vec<MoveValue>` to a `Vec<u8>` if the inner `MoveValue` is a `MoveValue::U8`,
/// or returns an error otherwise.
fn vec_to_vec_u8(vec: Vec<MoveValue>) -> Result<Vec<u8>> {
let mut vec_u8 = Vec::with_capacity(vec.len());

for byte in vec {
match byte {
MoveValue::U8(u8) => {
vec_u8.push(u8);
}
_ => {
return Err(anyhow!(
"Expected inner MoveValue in Vec<MoveValue> to be a MoveValue::U8".to_string(),
));
}
}
}
Ok(vec_u8)
}

#[cfg(test)]
mod tests {
use crate::converter::move_value_to_json;
use move_core_types::account_address::AccountAddress;
use move_core_types::value::MoveValue;
use serde_json::json;

#[test]
fn test_number_to_json() {
let u8_val = MoveValue::U8(0);
assert_eq!(move_value_to_json(u8_val), json!(0));

let u64_val = MoveValue::U64(0);
assert_eq!(move_value_to_json(u64_val), json!(0));

let u128_val = MoveValue::U128(0);
assert_eq!(move_value_to_json(u128_val), json!("0"));
}

#[test]
fn test_bool_to_json() {
let bool_val = MoveValue::Bool(true);
assert_eq!(move_value_to_json(bool_val), json!(true));
}
}
2 changes: 1 addition & 1 deletion src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn get_cache_path(cache_folder: String) -> String {
.unwrap();
}

pub fn get_function_module(
pub fn get_module(
client: Client,
module_id: &ModuleId,
network: String,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod config;
mod converter;
mod helper;
mod storage;
mod table;
Expand Down
46 changes: 38 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod config;
mod converter;
mod helper;
mod storage;
mod table;
Expand All @@ -7,6 +8,7 @@ mod types;
extern crate core;
extern crate log;

use std::borrow::Borrow;
use std::fs;

use simplelog::*;
Expand All @@ -17,6 +19,7 @@ use std::str::FromStr;

use anyhow::Result;
use aptos_gas::{AbstractValueSizeGasParameters, NativeGasParameters, LATEST_GAS_FEATURE_VERSION};
use aptos_sdk::rest_client::aptos_api_types::MoveType;
use aptos_sdk::rest_client::Client;

use clap::Parser;
Expand All @@ -25,7 +28,7 @@ use log::{debug, LevelFilter};
use move_core_types::account_address::AccountAddress;
use move_core_types::identifier::{IdentStr, Identifier};
use move_core_types::language_storage::{ModuleId, TypeTag, CORE_CODE_ADDRESS};
use move_core_types::value::MoveValue;
use move_core_types::value::{MoveStruct, MoveValue};
use move_vm_runtime::move_vm::MoveVM;
use move_vm_test_utils::gas_schedule::{CostTable, Gas, GasStatus};
use uuid::Uuid;
Expand All @@ -36,7 +39,8 @@ use move_vm_runtime::native_extensions::NativeContextExtensions;
use move_vm_runtime::native_functions::NativeFunctionTable;

use crate::config::{ConfigData, ToolConfig};
use crate::helper::{absolute_path, get_function_module, get_node_url, serialize_input_params};
use crate::converter::{annotate_value, move_value_to_json};
use crate::helper::{absolute_path, get_module, get_node_url, serialize_input_params};
use crate::storage::InMemoryLazyStorage;
use crate::types::{ExecutionResult, LogLevel, Network, ViewFunction};

Expand Down Expand Up @@ -143,7 +147,7 @@ fn exec_func(
let client = Client::new(get_node_url(network, config));

let cache_folder = config.cache_folder.clone().unwrap();
let (_, abi) = get_function_module(
let (_, abi) = get_module(
client.clone(),
&module,
format!("{}", network),
Expand All @@ -157,8 +161,8 @@ fn exec_func(
.into_iter()
.find(|f| f.name.to_string() == func_id.to_string());

let param_types = if let Some(f) = matched_func {
f.params
let (param_types, ret_types) = if let Some(f) = matched_func {
(f.params, f.return_)
} else {
panic!("No matched function found!");
};
Expand All @@ -175,12 +179,33 @@ fn exec_func(
ledger_version,
format!("{}", network),
client.clone(),
cache_folder,
cache_folder.clone(),
);
let res = exec_func_internal(storage, module, func_id, type_args, ser_args);
match res {
None => execution_res.return_values = vec![],
Some(vals) => execution_res.return_values = vals,
Some(vals) => {
let mut value_iter = vals.into_iter();
let mut type_iter = ret_types.into_iter();
let mut json_ret_vals = vec![];
loop {
let tpe = type_iter.next();
if let Some(t) = tpe {
let mut val = value_iter.next().unwrap();
val = annotate_value(
val,
&t,
client.clone(),
network.clone(),
cache_folder.clone(),
);
json_ret_vals.push(move_value_to_json(val));
} else {
break;
}
}
execution_res.return_values = json_ret_vals;
}
}
}

Expand Down Expand Up @@ -253,6 +278,7 @@ fn get_gas_status(cost_table: &CostTable, gas_budget: Option<u64>) -> Result<Gas

#[cfg(test)]
mod tests {
use crate::converter::move_value_to_json;
use crate::{
exec_func, exec_func_internal, get_node_url, ConfigData, ExecutionResult,
InMemoryLazyStorage, Network, ToolConfig,
Expand All @@ -264,6 +290,7 @@ mod tests {
use move_core_types::language_storage::{ModuleId, TypeTag};
use move_core_types::value::MoveValue;
use once_cell::sync::Lazy;
use serde_json::Value;
use simplelog::{Config, SimpleLogger};

static CONFIG: Lazy<ToolConfig> = Lazy::new(|| ConfigData::default().config);
Expand Down Expand Up @@ -379,7 +406,10 @@ mod tests {
&mut execution_result,
);
assert_eq!(execution_result.return_values.len(), 1);
assert_eq!(execution_result.return_values[0], MoveValue::U64(0));
assert_eq!(
execution_result.return_values[0],
serde_json::to_value(0).unwrap()
);
debug!("{}", execution_result.return_values[0]);
}
}
10 changes: 5 additions & 5 deletions src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use aptos_sdk::move_types::account_address::AccountAddress as AptosAccountAddress;
use aptos_sdk::move_types::language_storage::StructTag as AptosStructTag;

use crate::helper::get_function_module;
use crate::helper::get_module;
use anyhow::{bail, Error, Result};
use aptos_sdk::rest_client::aptos_api_types::mime_types::BCS;
use aptos_sdk::rest_client::Client;
Expand All @@ -13,13 +13,13 @@ use move_core_types::language_storage::{ModuleId, StructTag, TypeTag};
use move_core_types::resolver::{ModuleResolver, ResourceResolver};
use move_table_extension::{TableHandle, TableResolver};
use reqwest::header::ACCEPT;
use reqwest::StatusCode;
use std::collections::HashMap;
use std::str::FromStr;
use std::{
collections::{btree_map, BTreeMap},
fmt::Debug,
};
use reqwest::StatusCode;
use tokio::runtime::Runtime;

/// Simple in-memory storage for modules and resources under an account.
Expand Down Expand Up @@ -135,7 +135,7 @@ impl ModuleResolver for InMemoryLazyStorage {
}
}

let (mod_, _) = get_function_module(
let (mod_, _) = get_module(
self.client.clone(),
module_id,
self.network.clone(),
Expand Down Expand Up @@ -230,8 +230,8 @@ impl TableResolver for InMemoryLazyStorage {
.json(&map)
.send()
.unwrap();
if (resp.status() == StatusCode::NOT_FOUND) {
return Ok(None)
if resp.status() == StatusCode::NOT_FOUND {
return Ok(None);
}

let bytes = resp.bytes().unwrap();
Expand Down
3 changes: 2 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use clap::{command, Parser, ValueEnum};
use move_core_types::value::MoveValue;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::{Display, Formatter};

#[derive(Serialize, Debug)]
pub struct ExecutionResult {
pub(crate) log_path: String,
pub(crate) return_values: Vec<MoveValue>,
pub(crate) return_values: Vec<Value>,
}

#[derive(ValueEnum, Deserialize, Eq, PartialEq, Hash, Clone, Copy, Debug)]
Expand Down

0 comments on commit e34f71f

Please sign in to comment.