diff --git a/crates/executor/executor.rs b/crates/executor/executor.rs index e5a9ca6a..a31c2544 100644 --- a/crates/executor/executor.rs +++ b/crates/executor/executor.rs @@ -109,6 +109,19 @@ pub async fn op_smartweave_read_contract( pub fn generate_interaction_context( tx: &GQLNodeInterface, ) -> InteractionContext { + let tags = tx.tags.to_owned(); + + let specific_function = { + let data = tags + .into_iter() + .find(|tag| tag.name == String::from("3EM-Function")); + if let Some(specific_function_tag) = data { + Some(specific_function_tag.value) + } else { + None + } + }; + InteractionContext { transaction: InteractionTx { id: tx.id.to_owned(), @@ -142,6 +155,7 @@ pub fn generate_interaction_context( height: tx.block.height.to_owned(), timestamp: tx.block.timestamp.to_owned(), }, + specific_function, } } @@ -203,8 +217,11 @@ pub async fn raw_execute_contract< }); let interaction_context = generate_interaction_context(&tx); + let specific_fn = interaction_context.specific_function.clone(); - let valid = match rt.call(call_input, Some(interaction_context)).await + let valid = match rt + .call(call_input, Some(interaction_context), specific_fn) + .await { Ok(None) => serde_json::Value::Bool(true), Ok(Some(CallResult::Evolve(evolve))) => { @@ -674,6 +691,105 @@ mod tests { } } + #[tokio::test] + pub async fn test_executor_specific_function_js() { + let init_state = serde_json::json!({ + "users": [] + }); + + let fake_contract = generate_fake_loaded_contract_data( + include_bytes!( + "../../testdata/contracts/user_function_specific_contract.js" + ), + ContractType::JAVASCRIPT, + init_state.to_string(), + ); + let fake_interactions = vec![ + generate_fake_interaction( + serde_json::json!({ + "function": "addUser", + "username": "Andres" + }), + "tx1", + None, + None, + None, + None, + None, + None, + None, + None, + ), + generate_fake_interaction( + serde_json::json!({ + "username": "Tate" + }), + "tx2", + None, + None, + None, + None, + Some(GQLTagInterface { + name: String::from("3EM-Function"), + value: String::from("addUser"), + }), + None, + None, + None, + ), + ]; + + let execute = || async { + let result = raw_execute_contract( + String::new(), + fake_contract, + fake_interactions, + IndexMap::new(), + None, + true, + false, + |_, _| { + panic!("not implemented"); + }, + &Arweave::new( + 443, + "arweave.net".to_string(), + String::from("https"), + ArweaveCache::new(), + ), + HashMap::new(), + ) + .await; + + result + }; + + if let ExecuteResult::V8(value, validity, exm_context) = execute().await { + assert_eq!(validity.len(), 2); + let (tx1, tx2) = (validity.get("tx1"), validity.get("tx2")); + assert_eq!(tx1.is_some(), true); + assert_eq!(tx2.is_some(), true); + assert_eq!((tx1.unwrap()).to_owned(), true); + assert_eq!((tx2.unwrap()).to_owned(), true); + + let value_state = value.get("users"); + assert_eq!(value_state.is_some(), true); + let users = value_state + .unwrap() + .to_owned() + .as_array() + .unwrap() + .to_owned(); + assert_eq!( + users.get(0).unwrap().to_owned(), + serde_json::json!("Andres") + ); + assert_eq!(users.get(1).unwrap().to_owned(), serde_json::json!("Tate")); + } else { + panic!("Failed"); + } + } + #[tokio::test] async fn test_contract_evolve() { let init_state = serde_json::json!({ diff --git a/crates/js/lib.rs b/crates/js/lib.rs index 996311bd..0a54a544 100644 --- a/crates/js/lib.rs +++ b/crates/js/lib.rs @@ -204,6 +204,7 @@ impl Runtime { &mut self, action: R, interaction_data: Option, + specific_fn: Option, ) -> Result, AnyError> where R: Serialize + 'static, @@ -225,11 +226,13 @@ impl Runtime { } }; + let func_name = &specific_fn.unwrap_or(String::from("handle"))[..]; + let action: v8::Local = serde_v8::to_v8(scope, action).unwrap(); let module_obj = self.module.open(scope).to_object(scope).unwrap(); - let key = v8::String::new(scope, "handle").unwrap().into(); + let key = v8::String::new(scope, func_name).unwrap().into(); let func_obj = module_obj.get(scope, key).unwrap(); let func = v8::Local::::try_from(func_obj).unwrap(); @@ -334,7 +337,7 @@ mod test { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let value = rt.get_contract_state::().unwrap(); assert_eq!(value, -69); @@ -359,7 +362,7 @@ export async function handle(slice) { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let hash = rt.get_contract_state::<[u8; 20]>().unwrap(); assert_eq!( hash.to_vec(), @@ -398,7 +401,7 @@ try { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let calls = rt.get_exm_context::().unwrap(); let tx_id = rt.get_contract_state::().unwrap(); @@ -436,11 +439,11 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let rand1 = rt.get_contract_state::().unwrap(); assert_eq!(rand1, 0.3800000002095474); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let rand2 = rt.get_contract_state::().unwrap(); assert_eq!(rand2, 0.1933761369163034); } @@ -463,11 +466,11 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let rand1 = rt.get_contract_state::<[u8; 8]>().unwrap(); assert_eq!(rand1.as_ref(), &[127, 111, 44, 205, 178, 63, 42, 187]); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let rand2 = rt.get_contract_state::<[u8; 8]>().unwrap(); assert_eq!(rand2.as_ref(), &[123, 105, 39, 142, 148, 124, 1, 198]); } @@ -496,7 +499,7 @@ export async function handle() { .await .unwrap(); - rt.call(&(), None).await.unwrap(); + rt.call(&(), None, None).await.unwrap(); let gced = rt.get_contract_state::().unwrap(); assert_eq!(gced, false); } @@ -522,7 +525,7 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let exists = rt.get_contract_state::().unwrap(); assert_eq!(exists, true); } @@ -544,7 +547,7 @@ export async function handle() { .unwrap(); let err = rt - .call((), None) + .call((), None, None) .await .unwrap_err() .downcast::() @@ -572,7 +575,7 @@ export async function handle() { .await .unwrap(); - let evolved = rt.call((), None).await.unwrap(); + let evolved = rt.call((), None, None).await.unwrap(); assert_eq!(evolved, Some(CallResult::Evolve("xxdummy".to_string()))); } @@ -592,7 +595,7 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let host = rt.get_contract_state::().unwrap(); assert_eq!(host, "http://arweave.net:12345"); } @@ -625,7 +628,7 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let data = rt.get_contract_state::>().unwrap(); assert_eq!(data.get(0).unwrap(), "App-Name"); assert_eq!(data.get(1).unwrap(), "SmartWeaveContract"); @@ -663,7 +666,7 @@ export async function handle() { .await .unwrap(); - rt.call((), None).await.unwrap(); + rt.call((), None, None).await.unwrap(); let data = rt .get_contract_state::<(String, bool, deno_core::serde_json::Value)>() .unwrap(); @@ -707,7 +710,7 @@ try { interaction_data.transaction.id = String::from("YzVdaDBnaiGToFQJAnJCGtyJwJZbaCASotWEPFhBgBY"); - rt.call((), Some(interaction_data)).await.unwrap(); + rt.call((), Some(interaction_data), None).await.unwrap(); let host = rt.get_contract_state::>().unwrap(); assert_eq!(host.get(0).unwrap().to_owned(), String::from("eyJjb250ZW50Ijp7ImJvZHkiOiJoZWxsbyB3b3JsZCIsInRpbWVzdGFtcCI6MTY0MTYzNDQzOCwidGl0bGUiOiJoZWxsbyB3b3JsZCJ9LCJkaWdlc3QiOiJOLVJ6UmZXMmxzV0RqdV9GcE5RUzZhWmdzTzdma1Z5eVo3bWJzRWhpNjZ3IiwiYXV0aG9yc2hpcCI6eyJjb250cmlidXRvciI6IjB4ZWFlYjIyNjIwREJEOTgwRTc0NjNjOWQ5NkE1YWU0ZDk0NDhiMTMzYyIsInNpZ25pbmdLZXkiOiJ7XCJjcnZcIjpcIlAtMjU2XCIsXCJleHRcIjp0cnVlLFwia2V5X29wc1wiOltcInZlcmlmeVwiXSxcImt0eVwiOlwiRUNcIixcInhcIjpcImRVcEI3MVhJV1lZYjBQSGlqdkJicTNtMl9CNFA2aGZFZHNBdndkS0JDNk1cIixcInlcIjpcIlBHRmlveWtaODU4cHN3cEZXODZtdjZKQlBFZ1Y4WFNVZWY5M3pqNUFoNFFcIn0iLCJzaWduYXR1cmUiOiJ1WE9YWVJrX1dFVmdaM0xOR0xhWUVsNmVGYS1xa1JyWURwRjJ5ektwUXJhS1I1R0lqWEdiSXg4QUFZQ0VPNEZEQXhVeF93bjVkUWR1RldZZTNKTHEtUSIsInNpZ25pbmdLZXlTaWduYXR1cmUiOiIweDhhMmFjNTE3NTg5OTA2NDkwMWEzZTBlM2VjODc0ZjZmOTFjMWE3NzVjODM4NjNmMDY2OWE0YjUxMjhiYjgxODcwYTQzMmUwZTM1YjAwMjUxZTdmMTg0ZTRlYzE2ZTNjOWVkNDM0YjBjMjkzMDc3N2I3M2UzMzg3MDk5MWQ0NjhlMWIiLCJzaWduaW5nS2V5TWVzc2FnZSI6IkkgYXV0aG9yaXplIHB1Ymxpc2hpbmcgb24gbWlycm9yLnh5eiBmcm9tIHRoaXMgZGV2aWNlIHVzaW5nOlxue1wiY3J2XCI6XCJQLTI1NlwiLFwiZXh0XCI6dHJ1ZSxcImtleV9vcHNcIjpbXCJ2ZXJpZnlcIl0sXCJrdHlcIjpcIkVDXCIsXCJ4XCI6XCJkVXBCNzFYSVdZWWIwUEhpanZCYnEzbTJfQjRQNmhmRWRzQXZ3ZEtCQzZNXCIsXCJ5XCI6XCJQR0Zpb3lrWjg1OHBzd3BGVzg2bXY2SkJQRWdWOFhTVWVmOTN6ajVBaDRRXCJ9IiwiYWxnb3JpdGhtIjp7Im5hbWUiOiJFQ0RTQSIsImhhc2giOiJTSEEtMjU2In19LCJuZnQiOnt9LCJ2ZXJzaW9uIjoiMTItMjEtMjAyMCIsIm9yaWdpbmFsRGlnZXN0IjoiTi1SelJmVzJsc1dEanVfRnBOUVM2YVpnc083ZmtWeXlaN21ic0VoaTY2dyJ9")); assert_eq!( @@ -732,7 +735,7 @@ export async function handle() { .unwrap(); let result = rt - .call((), None) + .call((), None, None) .await .unwrap() .expect("Expected CallResult"); diff --git a/crates/smartweave/lib.rs b/crates/smartweave/lib.rs index 2719b72e..d0ef5c8d 100644 --- a/crates/smartweave/lib.rs +++ b/crates/smartweave/lib.rs @@ -46,6 +46,7 @@ pub struct InteractionBlock { pub struct InteractionContext { pub transaction: InteractionTx, pub block: InteractionBlock, + pub specific_function: Option, } pub fn init( diff --git a/testdata/contracts/user_function_specific_contract.js b/testdata/contracts/user_function_specific_contract.js new file mode 100644 index 00000000..6a648f5e --- /dev/null +++ b/testdata/contracts/user_function_specific_contract.js @@ -0,0 +1,17 @@ +export async function addUser(state, action) { + state.users.push(action.input.username); + return state; +} + +export async function handle(state, action) { + + if(action.input.function === 'addUser') { + state.users.push(action.input.username); + } else { + throw new Error("Invalid operation"); + } + + return { + state + }; +}