Skip to content

Commit

Permalink
feat: return view function result with events and used gas (#17)
Browse files Browse the repository at this point in the history
* return view function result with events and used gas

* fmt

* clippy

* use JsonEvent
  • Loading branch information
beer-1 authored Mar 21, 2024
1 parent 13861b1 commit 8469be0
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 38 deletions.
13 changes: 11 additions & 2 deletions crates/e2e-move-tests/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use bytes::Bytes;
use initia_move_compiler::built_package::BuiltPackage;
use initia_move_types::env::Env;
use initia_move_types::view_function::ViewFunction;
use initia_move_types::view_function::{ViewFunction, ViewOutput};
use move_core_types::account_address::AccountAddress;
use move_core_types::language_storage::{StructTag, TypeTag};
use move_core_types::vm_status::VMStatus;
Expand Down Expand Up @@ -133,6 +133,15 @@ impl MoveHarness {
}

pub fn run_view_function(&mut self, view_fn: ViewFunction) -> Result<String, VMStatus> {
let state = self.chain.create_state();
let output = self.run_view_function_with_state(view_fn, &state)?;
Ok(output.ret().clone())
}

pub fn run_view_function_get_events(
&mut self,
view_fn: ViewFunction,
) -> Result<ViewOutput, VMStatus> {
let state = self.chain.create_state();
self.run_view_function_with_state(view_fn, &state)
}
Expand All @@ -141,7 +150,7 @@ impl MoveHarness {
&mut self,
view_fn: ViewFunction,
state: &MockState,
) -> Result<String, VMStatus> {
) -> Result<ViewOutput, VMStatus> {
let mut table_state = MockTableState::new(state);

let resolver = StateViewImpl::new(state);
Expand Down
1 change: 1 addition & 0 deletions crates/e2e-move-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod output;
mod staking;
mod std_coin;
mod table;
mod view_output;

#[cfg(feature = "testing")]
mod move_unit;
10 changes: 10 additions & 0 deletions crates/e2e-move-tests/src/tests/view_output.data/pack/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "ViewOutputTests"
version = "0.0.0"

[dependencies]
InitiaStdlib = { local = "../../../../../../precompile/modules/initia_stdlib" }

[addresses]
std = "0x1"
test = "0x2"
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module test::ViewOutputTests {
use std::event;
use std::string;
use initia_std::type_info;

#[event]
/// Event emitted when some amount of coins are withdrawn from an Collateral.
struct ViewEvent has drop, store {
type_arg: string::String,
arg: string::String,
}

#[view]
public fun emit_event<TypeArg>(arg: string::String): string::String {
event::emit(ViewEvent {
type_arg: type_info::type_name<TypeArg>(),
arg,
});

arg
}
}
54 changes: 54 additions & 0 deletions crates/e2e-move-tests/src/tests/view_output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::str::FromStr;

use crate::MoveHarness;
use initia_move_types::json_event::JsonEvents;
use initia_move_types::view_function::ViewFunction;
use move_core_types::identifier::Identifier;
use move_core_types::language_storage::{StructTag, TypeTag};
use move_core_types::{account_address::AccountAddress, language_storage::ModuleId};

#[test]
fn test_view_output() {
let deployer_addr =
AccountAddress::from_hex_literal("0x2").expect("0x2 account should be created");
let path = "src/tests/view_output.data/pack";
let mut h = MoveHarness::new();

h.initialize();

// publish std coin
let output = h
.publish_package(&deployer_addr, path)
.expect("should success");
h.commit(output, true);

let module_name = Identifier::from_str("ViewOutputTests").unwrap();
let module_id = ModuleId::new(deployer_addr, module_name.clone());
let function_name = Identifier::from_str("emit_event").unwrap();
let struct_name = Identifier::from_str("ViewEvent").unwrap();

let arg_bytes = bcs::to_bytes("hello world").unwrap();
let out = h
.run_view_function_get_events(ViewFunction::new(
module_id,
function_name,
vec![TypeTag::U256],
vec![arg_bytes],
))
.expect("should success");

assert_eq!(out.ret().as_str(), "\"hello world\"");
assert_eq!(
out.events(),
&JsonEvents::new(vec![(
TypeTag::Struct(Box::new(StructTag {
address: deployer_addr,
module: module_name,
name: struct_name,
type_params: vec![]
})),
"{\"arg\":\"hello world\",\"type_arg\":\"u256\"}".to_string()
)])
.into_inner(),
);
}
23 changes: 23 additions & 0 deletions crates/types/src/json_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ pub struct JsonEvent {
event_data: String,
}

impl PartialEq for JsonEvent {
fn eq(&self, other: &Self) -> bool {
self.type_tag == other.type_tag && self.event_data == other.event_data
}
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct JsonEvents(Vec<(TypeTag, String)>);

Expand All @@ -32,3 +38,20 @@ impl AsRef<Vec<(TypeTag, String)>> for JsonEvents {
&self.0
}
}

impl PartialEq for JsonEvents {
fn eq(&self, other: &Self) -> bool {
if self.0.len() != other.0.len() {
return false;
}

for (i, my) in self.0.iter().enumerate() {
let other = other.0.get(i).unwrap();
if my != other {
return false;
}
}

true
}
}
28 changes: 0 additions & 28 deletions crates/types/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ pub fn genesis_address() -> AccountAddress {
CORE_CODE_ADDRESS
}

use anyhow::{format_err, Error, Result};

use move_core_types::account_address::AccountAddress;

use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

use crate::account::Accounts;
use crate::cosmos::CosmosMessages;
Expand Down Expand Up @@ -89,31 +86,6 @@ pub enum MessagePayload {
Script(Script),
}

#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[repr(u8)]
pub enum MessagePayloadType {
Execute = 1,
Script = 2,
}

impl TryFrom<u8> for MessagePayloadType {
type Error = Error;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(MessagePayloadType::Execute),
2 => Ok(MessagePayloadType::Script),
_ => Err(format_err!("invalid PayloadType")),
}
}
}

impl From<MessagePayloadType> for u8 {
fn from(t: MessagePayloadType) -> Self {
t as u8
}
}

#[derive(Default, Debug, Clone)]
pub struct MessageOutput {
events: JsonEvents,
Expand Down
32 changes: 32 additions & 0 deletions crates/types/src/view_function.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::json_event::JsonEvent;
use crate::serde_helper::vec_bytes;

use move_core_types::identifier::{IdentStr, Identifier};
Expand Down Expand Up @@ -49,3 +50,34 @@ impl ViewFunction {
(self.module, self.function, self.ty_args, self.args)
}
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct ViewOutput {
ret: String,
events: Vec<JsonEvent>,

/// The amount of gas used during execution.
gas_used: u64,
}

impl ViewOutput {
pub fn new(ret: String, events: Vec<JsonEvent>, gas_used: u64) -> Self {
ViewOutput {
ret,
events,
gas_used,
}
}

pub fn ret(&self) -> &String {
&self.ret
}

pub fn events(&self) -> &Vec<JsonEvent> {
&self.events
}

pub fn gas_used(&self) -> u64 {
self.gas_used
}
}
20 changes: 16 additions & 4 deletions crates/vm/src/move_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use initia_move_types::{
metadata::INIT_MODULE_FUNCTION_NAME,
module::ModuleBundle,
staking_change_set::StakingChangeSet,
view_function::ViewFunction,
view_function::{ViewFunction, ViewOutput},
write_set::WriteSet,
};

Expand Down Expand Up @@ -269,7 +269,7 @@ impl MoveVM {
table_view_impl: &mut TableViewImpl<'_, T>,
view_fn: &ViewFunction,
gas_limit: Gas,
) -> Result<String, VMStatus> {
) -> Result<ViewOutput, VMStatus> {
let mut session = self.create_session(api, env, state_view_impl, table_view_impl);

let func_inst =
Expand All @@ -296,8 +296,20 @@ impl MoveVM {
&mut gas_meter,
)?;

let output = serialize_response_to_json(res)?.expect("view function must return value");
Ok(output)
let session_output = session.finish()?;
let (events, _, _, _, _, session_cache) = session_output;
let json_events = self.serialize_events_to_json(events, &session_cache)?;
let ret = serialize_response_to_json(res)?.expect("view function must return value");
let gas_used = gas_meter
.gas_limit()
.checked_sub(gas_meter.balance())
.unwrap();

Ok(ViewOutput::new(
ret,
json_events.into_inner(),
gas_used.into(),
))
}

#[allow(clippy::too_many_arguments)]
Expand Down
16 changes: 15 additions & 1 deletion crates/vm/src/verifier/transaction_arg_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ fn construct_arg(
let initial_cursor_len = arg.len();
let mut cursor = Cursor::new(&arg[..]);
let mut new_arg = vec![];
let mut max_invocations = 10; // Read from config in the future
let mut max_invocations = 1000; // Read from config in the future
let max_depth = 10;
recursively_construct_arg(
session,
ty,
Expand All @@ -271,6 +272,7 @@ fn construct_arg(
initial_cursor_len,
gas_meter,
&mut max_invocations,
max_depth,
&mut new_arg,
)?;
// Check cursor has parsed everything
Expand Down Expand Up @@ -308,10 +310,18 @@ pub(crate) fn recursively_construct_arg(
initial_cursor_len: usize,
gas_meter: &mut impl GasMeter,
max_invocations: &mut u64,
max_depth: u64,
arg: &mut Vec<u8>,
) -> Result<(), VMStatus> {
use move_vm_types::loaded_data::runtime_types::Type::*;

if max_depth == 0 {
return Err(VMStatus::error(
StatusCode::FAILED_TO_DESERIALIZE_ARGUMENT,
None,
));
}

match ty {
Vector(inner) => {
// get the vector length and iterate over each element
Expand All @@ -326,6 +336,7 @@ pub(crate) fn recursively_construct_arg(
initial_cursor_len,
gas_meter,
max_invocations,
max_depth - 1,
arg,
)?;
len -= 1;
Expand All @@ -349,6 +360,7 @@ pub(crate) fn recursively_construct_arg(
initial_cursor_len,
gas_meter,
max_invocations,
max_depth,
)?);
}
Bool | U8 => read_n_bytes(1, cursor, arg)?,
Expand Down Expand Up @@ -376,6 +388,7 @@ fn validate_and_construct(
initial_cursor_len: usize,
gas_meter: &mut impl GasMeter,
max_invocations: &mut u64,
max_depth: u64,
) -> Result<Vec<u8>, VMStatus> {
if *max_invocations == 0 {
return Err(VMStatus::error(
Expand Down Expand Up @@ -443,6 +456,7 @@ fn validate_and_construct(
initial_cursor_len,
gas_meter,
max_invocations,
max_depth - 1,
&mut arg,
)?;
args.push(arg);
Expand Down
4 changes: 2 additions & 2 deletions libmovevm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub(crate) fn execute_view_function(
let state_view_impl = StateViewImpl::new(&storage);
let mut table_view_impl = TableViewImpl::new(&mut table_storage);

let return_val = vm.execute_view_function(
let output = vm.execute_view_function(
&api,
&env,
&state_view_impl,
Expand All @@ -141,7 +141,7 @@ pub(crate) fn execute_view_function(
gas_limit,
)?;

to_vec(&return_val)
to_vec(&output)
}

/////////////////////////////////////////
Expand Down
3 changes: 2 additions & 1 deletion tools/generate-bcs-go/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use initia_move_types::{
script::Script,
staking_change_set::StakingDelta,
table::TableInfo,
view_function::ViewFunction,
view_function::{ViewFunction, ViewOutput},
};
use move_core_types::{
account_address::AccountAddress,
Expand Down Expand Up @@ -45,6 +45,7 @@ fn main() {
tracer.trace_simple_type::<ExecutionResult>().unwrap();
tracer.trace_simple_type::<EntryFunction>().unwrap();
tracer.trace_simple_type::<ViewFunction>().unwrap();
tracer.trace_simple_type::<ViewOutput>().unwrap();
tracer.trace_simple_type::<ModuleBundle>().unwrap();
tracer.trace_simple_type::<Script>().unwrap();
tracer.trace_simple_type::<Env>().unwrap();
Expand Down
Loading

0 comments on commit 8469be0

Please sign in to comment.