diff --git a/crates/cli/dry_run.rs b/crates/cli/dry_run.rs index a5091fd2..9531db3e 100644 --- a/crates/cli/dry_run.rs +++ b/crates/cli/dry_run.rs @@ -140,14 +140,20 @@ pub async fn dry_run( ) -> Result<(), AnyError> { let execution = dry_run_result(port, host, protocol, file).await; - if let ExecuteResult::V8(value, validity_table, _) = execution { + if let ExecuteResult::V8(data) = execution { + let (state, validity, result) = ( + data.state, + data.validity, + data.result.unwrap_or(Value::default()), + ); let value = if show_validity { serde_json::json!({ - "state": value, - "validity": validity_table + "state": state, + "validity": validity, + "result": result }) } else { - value + state }; if pretty_print { @@ -185,7 +191,9 @@ mod tests { ) .await; - if let ExecuteResult::V8(value, validity_table, _) = execution { + if let ExecuteResult::V8(data) = execution { + let value = data.state; + let validity_table = data.validity; assert_eq!( value, serde_json::json!({ diff --git a/crates/cli/local_server.rs b/crates/cli/local_server.rs index 8f19c3ad..faf7db7f 100644 --- a/crates/cli/local_server.rs +++ b/crates/cli/local_server.rs @@ -70,18 +70,23 @@ async fn echo(req: Request) -> Result, hyper::Error> { match execute_result { Ok(result) => { match result { - ExecuteResult::V8(val, validity, _) => { + ExecuteResult::V8(data) => { + let val = data.state; + let validity = data.validity; + let result = data.result.unwrap_or(serde_json::Value::Null); if show_validity { response_result = Some(Response::new(Body::from( serde_json::json!({ "state": val, - "validity": validity + "validity": validity, + "result": result }).to_string() ))); } else { response_result = Some(Response::new(Body::from( serde_json::json!({ - "state": val + "state": val, + "result": result }).to_string() ))); } diff --git a/crates/cli/run.rs b/crates/cli/run.rs index b43f4c73..f9d11ed6 100644 --- a/crates/cli/run.rs +++ b/crates/cli/run.rs @@ -35,14 +35,19 @@ pub async fn run( } match execution { - ExecuteResult::V8(value, validity_table, _) => { + ExecuteResult::V8(data) => { + let state = data.state; + let validity_table = data.validity; + let result = data.result.unwrap_or(serde_json::Value::Null); + let value = if show_validity { serde_json::json!({ - "state": value, - "validity": validity_table + "state": state, + "validity": validity_table, + "result": result }) } else { - value + state }; if !no_print { diff --git a/crates/executor/executor.rs b/crates/executor/executor.rs index 5d23717b..93d42d32 100644 --- a/crates/executor/executor.rs +++ b/crates/executor/executor.rs @@ -30,9 +30,17 @@ use three_em_wasm::WasmRuntime; pub type ValidityTable = IndexMap; pub type CachedState = Option; +#[derive(Clone)] +pub struct V8Result { + pub state: Value, + pub result: Option, + pub validity: ValidityTable, + pub context: ExmContext, +} + #[derive(Clone)] pub enum ExecuteResult { - V8(Value, ValidityTable, ExmContext), + V8(V8Result), Evm(Storage, Vec, ValidityTable), } @@ -55,15 +63,15 @@ pub fn process_execution( show_validity: bool, ) -> Value { match execute_result { - ExecuteResult::V8(value, validity_table, exm_context) => { + ExecuteResult::V8(result) => { if show_validity { serde_json::json!({ - "state": value, - "validity": validity_table, - "exm": exm_context + "state": result.state, + "validity": result.validity, + "exm": result.context }) } else { - value + result.state } } ExecuteResult::Evm(store, result, validity_table) => { @@ -190,6 +198,8 @@ pub async fn raw_execute_contract< .await .unwrap(); + let mut latest_result: Option = None; + for interaction in interactions { let tx = interaction.node; @@ -208,52 +218,63 @@ pub async fn raw_execute_contract< let interaction_context = generate_interaction_context(&tx); - let valid = match rt - .call(call_input, Some(interaction_context)) - .await - { - Ok(None) => serde_json::Value::Bool(true), - Ok(Some(CallResult::Evolve(evolve))) => { - let contract = shared_client - .load_contract( - contract_id.clone(), - Some(evolve), - None, - None, - true, - false, - false, + let valid = + match rt.call(call_input, Some(interaction_context)).await { + Ok(None) => { + latest_result = None; + serde_json::Value::Bool(true) + } + Ok(Some(CallResult::Evolve(evolve))) => { + let contract = shared_client + .load_contract( + contract_id.clone(), + Some(evolve), + None, + None, + true, + false, + false, + ) + .await + .unwrap(); + + let state: Value = rt.get_contract_state().unwrap(); + rt = Runtime::new( + &(String::from_utf8_lossy(&contract.contract_src)), + state, + arweave_info.to_owned(), + op_smartweave_read_contract::decl(), + settings.clone(), + maybe_exm_context.clone(), ) .await .unwrap(); - let state: Value = rt.get_contract_state().unwrap(); - rt = Runtime::new( - &(String::from_utf8_lossy(&contract.contract_src)), - state, - arweave_info.to_owned(), - op_smartweave_read_contract::decl(), - settings.clone(), - maybe_exm_context.clone(), - ) - .await - .unwrap(); - - serde_json::Value::Bool(true) - } - Ok(Some(CallResult::Result(_))) => serde_json::Value::Bool(true), - Err(err) => { - if show_errors { - println!("{}", err); - } + latest_result = None; - if show_errors { - serde_json::Value::String(err.to_string()) - } else { - serde_json::Value::Bool(false) + serde_json::Value::Bool(true) } - } - }; + Ok(Some(CallResult::Result(result_value))) => { + let result_to_value = rt.to_value::(&result_value); + if result_to_value.is_ok() { + latest_result = Some(result_to_value.unwrap()); + } + serde_json::Value::Bool(true) + } + Err(err) => { + latest_result = None; + + if show_errors { + println!("{}", err); + } + + if show_errors { + serde_json::Value::String(err.to_string()) + } else { + serde_json::Value::Bool(false) + } + } + }; validity.insert(tx.id, valid); } else { validity.insert(tx.id, deno_core::serde_json::Value::Bool(false)); @@ -274,7 +295,12 @@ pub async fn raw_execute_contract< ); } - ExecuteResult::V8(state_val, validity, exm_context) + ExecuteResult::V8(V8Result { + state: state_val, + result: latest_result, + validity, + context: exm_context, + }) } else { on_cached(validity, cache_state) } @@ -348,7 +374,12 @@ pub async fn raw_execute_contract< ); } - ExecuteResult::V8(state, validity, Default::default()) + ExecuteResult::V8(V8Result { + state, + validity, + context: Default::default(), + result: None, + }) } else { on_cached(validity, cache_state) } @@ -496,9 +527,9 @@ mod tests { ) .await; - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { assert_eq!( - value, + result.state, serde_json::json!({"txId":"tx1123123123123123123213213123","txOwner":"SUPERMAN1293120","txTarget":"RECIPIENT1234","txQuantity":"100","txReward":"100","txTags":[{"name":"Input","value":"{}"},{"name":"MyTag","value":"Christpoher Nolan is awesome"}],"txHeight":2,"txIndepHash":"ABCD-EFG","txTimestamp":12301239,"winstonToAr":true,"arToWinston":true,"compareArWinston":1}) ); } else { @@ -506,6 +537,105 @@ mod tests { } } + #[tokio::test] + async fn test_counter_result_js() { + let init_state = serde_json::json!({ + "counts": 0 + }); + + let fake_contract = generate_fake_loaded_contract_data( + include_bytes!("../../testdata/contracts/counter.js"), + ContractType::JAVASCRIPT, + init_state.to_string(), + ); + + let mut transaction1 = generate_fake_interaction( + serde_json::json!({}), + "tx1123123123123123123213213123", + Some(String::from("ABCD-EFG")), + Some(2), + Some(String::from("SUPERMAN1293120")), + Some(String::from("RECIPIENT1234")), + Some(GQLTagInterface { + name: String::from("MyTag"), + value: String::from("Christpoher Nolan is awesome"), + }), + Some(GQLAmountInterface { + winston: Some(String::from("100")), + ar: None, + }), + Some(GQLAmountInterface { + winston: Some(String::from("100")), + ar: None, + }), + Some(12301239), + ); + + let fake_interactions = vec![transaction1.clone()]; + let fake_interactions_2 = vec![transaction1.clone(), transaction1.clone()]; + + let result = raw_execute_contract( + String::from("10230123021302130"), + fake_contract.clone(), + fake_interactions, + IndexMap::new(), + None, + true, + false, + |_, _| { + panic!("not implemented"); + }, + &Arweave::new( + 443, + "arweave.net".to_string(), + String::from("https"), + ArweaveCache::new(), + ), + HashMap::new(), + None, + ) + .await; + + if let ExecuteResult::V8(result) = result { + assert_eq!(result.result.is_some(), true); + assert_eq!( + result.result.unwrap(), + Value::String(String::from("Some result")) + ); + } else { + panic!("Unexpected entry"); + } + + let result = raw_execute_contract( + String::from("10230123021302130"), + fake_contract, + fake_interactions_2, + IndexMap::new(), + None, + true, + false, + |_, _| { + panic!("not implemented"); + }, + &Arweave::new( + 443, + "arweave.net".to_string(), + String::from("https"), + ArweaveCache::new(), + ), + HashMap::new(), + None, + ) + .await; + + if let ExecuteResult::V8(result) = result { + assert_eq!(result.state.get("counts").unwrap().as_i64().unwrap(), 2); + assert_eq!(result.result.is_some(), false); + } else { + panic!("Unexpected entry"); + } + } + #[tokio::test] async fn test_js_read_contract() { let init_state = serde_json::json!({}); @@ -562,12 +692,12 @@ mod tests { ) .await; - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { let x = serde_json::json!({ - "state": value, - "validity": validity + "state": result.state, + "validity": result.validity }); - assert_eq!(value["ticker"], serde_json::json!("ARCONFT67")); + assert_eq!(result.state["ticker"], serde_json::json!("ARCONFT67")); } else { panic!("Unexpected entry"); } @@ -658,7 +788,10 @@ mod tests { result }; - if let ExecuteResult::V8(value, validity, exm_context) = execute().await { + if let ExecuteResult::V8(result) = execute().await { + let validity = result.validity; + let value = result.state; + assert_eq!(validity.len(), 3); let (tx1, tx2, tx3) = ( validity.get("tx1"), @@ -685,6 +818,8 @@ mod tests { serde_json::json!("Andres") ); assert_eq!(users.get(1).unwrap().to_owned(), serde_json::json!("Divy")); + assert_eq!(result.result.is_some(), true); + assert_eq!(result.result.unwrap(), 2); } else { panic!("Failed"); } @@ -760,7 +895,9 @@ mod tests { ) .await; - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { + let validity = result.validity; + let value = result.state; // tx1 is the evolve action. This must not fail. // All network calls happen here and runtime is // re-initialized. @@ -839,7 +976,8 @@ mod tests { ) .await; - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { + let value = result.state; assert_eq!(value.get("txId").unwrap(), "STARWARS"); assert_eq!(value.get("owner").unwrap(), "ADDRESS2"); assert_eq!(value.get("height").unwrap(), 200); diff --git a/crates/executor/lib.rs b/crates/executor/lib.rs index 2323a6d5..850013e3 100644 --- a/crates/executor/lib.rs +++ b/crates/executor/lib.rs @@ -2,9 +2,9 @@ pub mod executor; pub mod test_util; pub mod utils; -use crate::executor::raw_execute_contract; pub use crate::executor::ExecuteResult; pub use crate::executor::ValidityTable; +use crate::executor::{raw_execute_contract, V8Result}; use deno_core::error::{generic_error, AnyError}; use deno_core::serde_json::Value; pub use indexmap::map::IndexMap; @@ -89,11 +89,12 @@ pub async fn simulate_contract( true, false, |validity_table, cache_state| { - ExecuteResult::V8( - cache_state.unwrap(), - validity_table, - Default::default(), - ) + ExecuteResult::V8(V8Result { + state: cache_state.unwrap(), + validity: validity_table, + context: Default::default(), + result: None, + }) }, arweave, settings, @@ -202,11 +203,12 @@ pub async fn execute_contract( needs_processing, show_errors, |validity_table, cache_state| { - ExecuteResult::V8( - cache_state.unwrap(), - validity_table, - Default::default(), - ) + ExecuteResult::V8(V8Result { + state: cache_state.unwrap(), + validity: validity_table, + context: Default::default(), + result: None, + }) }, arweave, HashMap::new(), @@ -496,7 +498,8 @@ mod test { .await .unwrap(); - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { + let validity = result.validity; let map: IndexMap = validity; let keys = map.keys(); @@ -540,7 +543,9 @@ mod test { ) .await .unwrap(); - if let ExecuteResult::V8(value, validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { + let value = result.state; + let validity = result.validity; assert!(!(value.is_null())); assert!(value.get("counter").is_some()); let counter = value.get("counter").unwrap().as_i64().unwrap(); @@ -575,7 +580,8 @@ mod test { ) .await .unwrap(); - if let ExecuteResult::V8(value, _validity, exm_context) = result { + if let ExecuteResult::V8(result) = result { + let value = result.state; assert!(!(value.is_null())); assert!(value.get("people").is_some()); assert!(value.get("people").unwrap().is_array()); diff --git a/crates/js/lib.rs b/crates/js/lib.rs index d35d3c57..53b77608 100644 --- a/crates/js/lib.rs +++ b/crates/js/lib.rs @@ -78,6 +78,9 @@ pub struct Runtime { is_promise: Option, /// Current state value. contract_state: v8::Global, + + /// Whether the current runtime belongs to EXM execution + is_exm: bool, } impl Runtime { @@ -120,7 +123,7 @@ impl Runtime { ..Default::default() }), three_em_smartweave::init(arweave, op_smartweave_read_state), - three_em_exm_base_ops::init(executor_settings), + three_em_exm_base_ops::init(executor_settings.clone()), ], module_loader: Some(module_loader), startup_snapshot: Some(snapshot::snapshot()), @@ -179,12 +182,18 @@ impl Runtime { ); } + let exm_setting_val = executor_settings + .get("EXM") + .unwrap_or_else(|| &Value::Bool(false)); + let is_exm = exm_setting_val.as_bool().unwrap(); + Ok(Self { rt, module, state, is_promise: None, contract_state, + is_exm, }) } @@ -196,6 +205,18 @@ impl Runtime { self.rt.handle_scope() } + pub fn to_value( + &mut self, + global_value: &v8::Global, + ) -> Result + where + T: DeserializeOwned + 'static, + { + let scope = &mut self.rt.handle_scope(); + let value = v8::Local::new(scope, global_value.clone()); + Ok(serde_v8::from_v8(scope, value)?) + } + pub fn get_contract_state(&mut self) -> Result where T: DeserializeOwned + 'static, @@ -282,37 +303,53 @@ impl Runtime { }; { + let mut result_act: Option> = None; // Run the event loop. let global = self.rt.resolve_value(global).await?; + // let data = self.get_contract_state::().unwrap(); + // println!("{}", data.to_string()); + let scope = &mut self.rt.handle_scope(); - let state = v8::Local::new(scope, global) - .to_object(scope) - .ok_or(Error::Terminated)?; - let state_key = v8::String::new(scope, "state").unwrap().into(); + let state_obj_local = v8::Local::new(scope, global).to_object(scope); - // Return value. - let result_key = v8::String::new(scope, "result").unwrap().into(); - let result = state.get(scope, result_key).unwrap(); - if !result.is_null_or_undefined() { - return Ok(Some(CallResult::Result(v8::Global::new(scope, result)))); - } + if let Some(state) = state_obj_local { + let state_key = v8::String::new(scope, "state").unwrap().into(); + + // Return value. + let result_key = v8::String::new(scope, "result").unwrap().into(); + let result = state.get(scope, result_key).unwrap(); + if !result.is_null_or_undefined() { + result_act = Some(v8::Global::new(scope, result)); + } + + if let Some(state_obj) = state.get(scope, state_key) { + if let Some(state) = state_obj.to_object(scope) { + // Update the contract state. + if !state_obj.is_null_or_undefined() { + self.contract_state = v8::Global::new(scope, state_obj); + + if !self.is_exm { + // Contract evolution. + let evolve_key = + v8::String::new(scope, "canEvolve").unwrap().into(); + let can_evolve = state.get(scope, evolve_key).unwrap(); + if can_evolve.boolean_value(scope) { + let evolve_key = + v8::String::new(scope, "evolve").unwrap().into(); + let evolve = state.get(scope, evolve_key).unwrap(); + return Ok(Some(CallResult::Evolve( + evolve.to_rust_string_lossy(scope), + ))); + } + } + } + } + } - let state_obj = state.get(scope, state_key).unwrap(); - if let Some(state) = state_obj.to_object(scope) { - // Update the contract state. - self.contract_state = v8::Global::new(scope, state_obj); - - // Contract evolution. - let evolve_key = v8::String::new(scope, "canEvolve").unwrap().into(); - let can_evolve = state.get(scope, evolve_key).unwrap(); - if can_evolve.boolean_value(scope) { - let evolve_key = v8::String::new(scope, "evolve").unwrap().into(); - let evolve = state.get(scope, evolve_key).unwrap(); - return Ok(Some(CallResult::Evolve( - evolve.to_rust_string_lossy(scope), - ))); + if let Some(result_v8_val) = result_act { + return Ok(Some(CallResult::Result(result_v8_val))); } } }; @@ -330,7 +367,7 @@ mod test { use deno_core::error::AnyError; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; - use deno_core::serde_json::Value; + use deno_core::serde_json::{json, Value}; use deno_core::OpState; use deno_core::ZeroCopyBuf; use deno_ops::op; @@ -365,6 +402,34 @@ mod test { assert_eq!(value, -69); } + #[tokio::test] + async fn test_state_empty() { + let mut rt = Runtime::new( + r#"export async function handle(state, action) { + state.data++; + state.data = Number(100 * 2 + state.data); + if(state.data > 300) { + return { state }; + } + }"#, + json!({ + "data": 0 + }), + (80, String::from("arweave.net"), String::from("https")), + never_op::decl(), + HashMap::new(), + None, + ) + .await + .unwrap(); + + rt.call((), None).await.unwrap(); + rt.call((), None).await.unwrap(); + let value = rt.get_contract_state::().unwrap(); + let number = value.get("data").unwrap().as_i64().unwrap(); + assert_eq!(number, 402); + } + #[tokio::test] async fn test_runtime_smartweave() { let buf: Vec = vec![0x00]; diff --git a/js/napi/index.d.ts b/js/napi/index.d.ts index 78d7f3e6..038c7f4b 100644 --- a/js/napi/index.d.ts +++ b/js/napi/index.d.ts @@ -3,8 +3,15 @@ /* auto-generated by NAPI-RS */ +export class ExternalObject { + readonly '': { + readonly '': unique symbol + [K: symbol]: T + } +} export interface ExecuteContractResult { state: any + result: any validity: Record exmContext: any } diff --git a/js/napi/napi.test.js b/js/napi/napi.test.js index 1e713e00..a763e0f4 100644 --- a/js/napi/napi.test.js +++ b/js/napi/napi.test.js @@ -63,7 +63,7 @@ describe("NAPI test", () => { export async function handle(state, action) { const { username } = action.input; state.users.push({ username }); - return state; + return { state, result: 'Hello World' }; } `); @@ -90,6 +90,6 @@ export async function handle(state, action) { ) }); expect(simulate.state.users).toEqual([{ username: "Andres" }]); - + expect(simulate.result).toEqual("Hello World"); }); }) diff --git a/js/napi/src/lib.rs b/js/napi/src/lib.rs index 368b3b97..505350d2 100644 --- a/js/napi/src/lib.rs +++ b/js/napi/src/lib.rs @@ -8,13 +8,13 @@ use three_em_arweave::arweave::{Arweave, ManualLoadedContract}; use three_em_arweave::cache::ArweaveCache; use three_em_arweave::cache::CacheExt; use three_em_arweave::gql_result::{GQLEdgeInterface, GQLTagInterface}; +use three_em_arweave::miscellaneous::ContractType; use three_em_executor::execute_contract as execute; use three_em_executor::simulate_contract as simulate; use three_em_executor::utils::create_simulated_transaction; use three_em_executor::ExecuteResult; use three_em_executor::ValidityTable; use tokio::runtime::Handle; -use three_em_arweave::miscellaneous::ContractType; #[cfg(target_os = "macos")] #[global_allocator] @@ -23,6 +23,7 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; #[napi(object)] pub struct ExecuteContractResult { pub state: serde_json::Value, + pub result: serde_json::Value, pub validity: HashMap, pub exm_context: serde_json::Value, } @@ -134,9 +135,16 @@ fn get_result( ) -> Option { if process_result.is_ok() { match process_result.unwrap() { - ExecuteResult::V8(state, validity, exm_context) => { + ExecuteResult::V8(data) => { + let (state, result, validity, exm_context) = ( + data.state, + data.result.unwrap_or(Value::Null), + data.validity, + data.context, + ); Some(ExecuteContractResult { state, + result, validity: validity_to_hashmap(validity), exm_context: serde_json::to_value(exm_context).unwrap(), }) @@ -162,7 +170,7 @@ async fn simulate_contract( maybe_bundled_contract, maybe_settings, maybe_exm_context, - maybe_contract_source + maybe_contract_source, } = context; let result = tokio::task::spawn_blocking(move || { @@ -216,8 +224,8 @@ async fn simulate_contract( contract_src: contract_source.contract_src.into(), contract_type: match contract_source.contract_type { SimulateContractType::JAVASCRIPT => ContractType::JAVASCRIPT, - SimulateContractType::WASM => ContractType::WASM - } + SimulateContractType::WASM => ContractType::WASM, + }, }; Some(loaded_contract) } else { @@ -234,7 +242,7 @@ async fn simulate_contract( maybe_bundled_contract, maybe_settings, maybe_exm_context, - manual_loaded_contract + manual_loaded_contract, ) .await; @@ -297,7 +305,11 @@ async fn execute_contract( #[cfg(test)] mod tests { - use crate::{execute_contract, get_gateway, simulate_contract, ExecuteConfig, SimulateExecutionContext, SimulateInput, ContractSource, SimulateContractType}; + use crate::{ + execute_contract, get_gateway, simulate_contract, ContractSource, + ExecuteConfig, SimulateContractType, SimulateExecutionContext, + SimulateInput, + }; use three_em_arweave::arweave::get_cache; // #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -354,7 +366,7 @@ mod tests { maybe_bundled_contract: None, maybe_settings: None, maybe_exm_context: None, - maybe_contract_source: None + maybe_contract_source: None, }; let contract = simulate_contract(execution_context).await.unwrap(); @@ -397,7 +409,7 @@ mod tests { maybe_bundled_contract: Some(true), maybe_settings: None, maybe_exm_context: None, - maybe_contract_source: None + maybe_contract_source: None, }; let contract = simulate_contract(execution_context).await.unwrap(); @@ -409,39 +421,45 @@ mod tests { #[tokio::test] pub async fn simulate_contract_test_custom_source() { - let contract_source_bytes = include_bytes!("../../../testdata/contracts/user-registry2.js"); + let contract_source_bytes = + include_bytes!("../../../testdata/contracts/user-registry2.js"); let contract_source_vec = contract_source_bytes.to_vec(); let execution_context: SimulateExecutionContext = - SimulateExecutionContext { - contract_id: String::new(), - interactions: vec![SimulateInput { - id: String::from("abcd"), - owner: String::from("210392sdaspd-asdm-asd_sa0d1293-lc"), - quantity: String::from("12301"), - reward: String::from("12931293"), - target: None, - tags: vec![], - block: None, - input: serde_json::json!({ - "username": "Andres" - }).to_string(), - }], - contract_init_state: Some(r#"{"users": []}"#.into()), - maybe_config: None, - maybe_cache: Some(false), - maybe_bundled_contract: None, - maybe_settings: None, - maybe_exm_context: None, - maybe_contract_source: Some(ContractSource { - contract_src: contract_source_vec.into(), - contract_type: SimulateContractType::JAVASCRIPT + SimulateExecutionContext { + contract_id: String::new(), + interactions: vec![SimulateInput { + id: String::from("abcd"), + owner: String::from("210392sdaspd-asdm-asd_sa0d1293-lc"), + quantity: String::from("12301"), + reward: String::from("12931293"), + target: None, + tags: vec![], + block: None, + input: serde_json::json!({ + "username": "Andres" }) - }; + .to_string(), + }], + contract_init_state: Some(r#"{"users": []}"#.into()), + maybe_config: None, + maybe_cache: Some(false), + maybe_bundled_contract: None, + maybe_settings: None, + maybe_exm_context: None, + maybe_contract_source: Some(ContractSource { + contract_src: contract_source_vec.into(), + contract_type: SimulateContractType::JAVASCRIPT, + }), + }; let contract = simulate_contract(execution_context).await.unwrap(); let contract_result = contract.state; println!("{}", contract_result); - assert_eq!(contract_result.get("users").unwrap(), &serde_json::json!([{"username": "Andres"}])); + assert_eq!( + contract_result.get("users").unwrap(), + &serde_json::json!([{"username": "Andres"}]) + ); + assert_eq!(contract.result.as_str().unwrap(), "Hello World"); } } diff --git a/testdata/contracts/counter.js b/testdata/contracts/counter.js new file mode 100644 index 00000000..5575d6ba --- /dev/null +++ b/testdata/contracts/counter.js @@ -0,0 +1,8 @@ +export async function handle(state, action) { + state.counts++; + let stateObj = { state }; + if(state.counts <= 1) { + stateObj.result = 'Some result'; + } + return stateObj; +} diff --git a/testdata/contracts/user-registry2.js b/testdata/contracts/user-registry2.js index c45db121..bbce8053 100644 --- a/testdata/contracts/user-registry2.js +++ b/testdata/contracts/user-registry2.js @@ -2,10 +2,10 @@ * * @param state is the current state your application holds * @param action is an object containing { input, caller } . Most of the times you will only use `action.input` which contains the input passed as a write operation - * @returns {Promise<{ users: Array<{ username: string}> }>} + * @returns {Promise<{result: string, state}>} */ export async function handle(state, action) { const { username } = action.input; state.users.push({ username }); - return state; + return { state, result: 'Hello World' }; } \ No newline at end of file diff --git a/testdata/contracts/users_contract.js b/testdata/contracts/users_contract.js index dfdd1885..90fae5de 100644 --- a/testdata/contracts/users_contract.js +++ b/testdata/contracts/users_contract.js @@ -6,6 +6,7 @@ export async function handle(state, action) { } return { - state + state, + result: state.users.length }; }