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
};
}