diff --git a/crates/exm/base.js b/crates/exm/base.js index aaca3069..65dd24e4 100644 --- a/crates/exm/base.js +++ b/crates/exm/base.js @@ -147,7 +147,7 @@ } else { try { if (this.requests[reqHash]) { - return Object.freeze(this.requests[reqHash]) + return Object.freeze(BaseReqResponse.from(this.requests[reqHash])) } else { const fetchData = await props.fetch(...args); const buff = await fetchData.arrayBuffer(); diff --git a/js/napi/src/lib.rs b/js/napi/src/lib.rs index 7c17fa94..b723b009 100644 --- a/js/napi/src/lib.rs +++ b/js/napi/src/lib.rs @@ -553,4 +553,46 @@ mod tests { let str_state = contract_result.to_string(); assert!(str_state.contains("wearemintingyes")); } + + #[tokio::test] + pub async fn simulate_contract_ark() { + let contract_source_bytes = + include_bytes!("../../../testdata/contracts/ark.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!({ + "function": "createContainer", + "caller_address": "0x197f818c1313dc58b32d88078ecdfb40ea822614", + "type": "evm", + "label": "test-evm", + "sig": "0x0e8cda3652185efcb9e68cbd932836c46df14dcf3b052931f463f0cd189a0fdd619206accafbb44afc155ce1c629da2eda4370fd71376ad250f28b50711b112b1c" + }).to_string(), + }], + contract_init_state: Some(String::from(include_str!("../../../testdata/contracts/ark.json"))), + 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(); + assert_eq!(contract.errors.len(), 0); + let contract_result = contract.state; + let str_state = contract_result.to_string(); + } } diff --git a/testdata/contracts/ark.js b/testdata/contracts/ark.js new file mode 100644 index 00000000..3eb24e5a --- /dev/null +++ b/testdata/contracts/ark.js @@ -0,0 +1,252 @@ +export async function handle(state, action) { + const input = action.input; + + const signatures = state.signatures; + let message_counter = state.message_counter; + + // if (input.function === "createContainer") { + // const { caller_address, sig, type, label } = input; + // ContractAssert( + // caller_address && sig && type, + // "ERROR_MISSING_REQUIRED_ARGUMENTS" + // ); + + + // const caller = type === "ar" ? await _ownerToAddress(caller_address) : caller_address; + + // ContractAssert( + // typeof label === "string" && label.trim().length, + // "ERROR_INVALID_CONTAINER_LABEL" + // ); + + // type === "ar" + // ? await _verifyArSignature(caller_address, sig, state.messages.ar) + // : await _moleculeSignatureVerification( + // caller_address, + // btoa(state.messages.evm + state.message_counter), + // sig, + // type + // ); + + // const timestamp = EXM.getDate().getTime(); + + // state.containers.push({ + // id: SmartWeave.transaction.id, + // label: label.trim(), + // controller_address: caller, + // network: type, + // first_linkage: timestamp, + // last_modification: timestamp, + // addresses: [{ address: caller, network: type, proof: sig }], + // vouched_by: [], + // }); + + // return { state }; + // } + + if (input.function === "createContainer") { + try { + const { caller_address, sig, type, label } = input; + const callerMessage = btoa(state.messages.evm + state.message_counter) + ContractAssert( + caller_address && sig && type, + "ERROR_MISSING_REQUIRED_ARGUMENTS" + ); + + let caller; + EXM.print("createContainer @ 1\n") + if (type === "ar") { + caller = await _ownerToAddress(caller_address) + } else { + caller = await _moleculeAddr(caller_address, callerMessage, sig, type); + } + EXM.print(`createContainer @ 2 -- caller resolved: ${caller}\n`) + + + // const caller = type === "ar" ? await _ownerToAddress(caller_address) : await + // ContractAssert( + // typeof label === "string" && label.trim().length, + // "ERROR_INVALID_CONTAINER_LABEL" + // ); + + type === "ar" + ? await _verifyArSignature(caller_address, sig, state.messages.ar) + : await _moleculeSignatureVerification( + caller_address, + btoa(state.messages.evm + state.message_counter), + sig, + type + ); + + EXM.print("createContainer @ 3\n") + + const timestamp = EXM.getDate().getTime(); + + state.containers.push({ + id: SmartWeave.transaction.id, + label: label.trim(), + controller_address: caller, + network: type, + first_linkage: timestamp, + last_modification: timestamp, + addresses: [{ address: caller, network: type, proof: sig }], + vouched_by: [], + }); + + EXM.print("createContainer @ 4\n") + + return { state }; + } catch(error) { + EXM.print(error) + throw new ContractError("error") + } + } + + + // UTILS + + function _getContainerIndex(id) { + const index = state.containers.findIndex( + (container) => container.id === id + ); + ContractAssert(index >= 0, "ERROR_INVALID_CONTAINER_ID"); + return index; + } + + async function _ownerToAddress(pubkey) { + try { + const req = await EXM.deterministicFetch( + `${state.molecule_endpoints.ar}/${pubkey}` + ); + const address = req.asJSON()?.address; + _validateArweaveAddress(address); + return address; + } catch (error) { + throw new ContractError("ERROR_MOLECULE_SERVER_ERROR"); + } + } + + async function _typeToMolecule(type) { + switch (type) { + case "evm": + return `${state.molecule_endpoints.evm}`; + case "sol": + return `${state.molecule_endpoints.sol}`; + case "tez": + return `${state.molecule_endpoints.tez}`; + } + } + + async function _getStateAddresses() { + return state.containers + .map((container) => container.addresses) + .flat() + .map((obj) => obj.address); + } + + async function _moleculeSignatureVerification( + caller, + message, + signature, + type + ) { + try { + EXM.print(`_moleculeSignatureVerification @ 1`) + ContractAssert(!signatures.includes(signature)); + const moleculeEndpoint = await _typeToMolecule(type); + const endpoint = `${moleculeEndpoint}/${caller}/${message}/${signature}`; + EXM.print(endpoint); + const isValid = await EXM.deterministicFetch( + endpoint + ); + EXM.print(isValid); + EXM.print(`_moleculeSignatureVerification @ 2`) + // EXM.print("molecule here 1") + // EXM.print(`here---> ${isValid.asJSON()}`) + // EXM.print(`result: ${isValid.asJSON()?.result}`) + EXM.print(`_moleculeSignatureVerification @ 3: ${isValid.asJSON()}`) + ContractAssert(isValid.asJSON()?.result, "ERROR_INVALID_CALLER"); + EXM.print(`_moleculeSignatureVerification @ 4`) + signatures.push(signature); + state.message_counter += 1; + // EXM.print("molecule here 1") + if (isValid.asJSON()?.address) { + return isValid.asJSON()?.address; + } + return caller; + } catch (error) { + EXM.print(error.stack) + throw new ContractError("ERROR_MOLECULE_CONNECTION"); + } + } + + + async function _moleculeAddr( + caller, + message, + signature, + type + ) { + try { + EXM.print("_moleculeAddr @ 1\n") + const moleculeEndpoint = await _typeToMolecule(type); + const isValid = await EXM.deterministicFetch( // mask the DF and the code will work + `${moleculeEndpoint}/${caller}/${message}/${signature}` + ); + EXM.print("_moleculeAddr @ 2\n") + + if (isValid.asJSON()?.address) { + return isValid.asJSON()?.address; + } + // EXM.print(isValid.asJSON()) + // EXM.print(caller) + EXM.print("_moleculeAddr @ 3\n") + return caller; + + } catch (error) { + EXM.print("error") + throw new ContractError("ERROR_MOLECULE_CONNECTION"); + } + } + + + async function _verifyArSignature(owner, signature, message) { + try { + _validatePubKeySyntax(owner); + + const encodedMessage = new TextEncoder().encode(message); + const typedArraySig = Uint8Array.from(atob(signature), (c) => + c.charCodeAt(0) + ); + const isValid = await SmartWeave.arweave.crypto.verify( + owner, + encodedMessage, + typedArraySig + ); + + ContractAssert(isValid, "ERROR_INVALID_CALLER_SIGNATURE"); + ContractAssert( + !state.signatures.includes(signature), + "ERROR_SIGNATURE_ALREADY_USED" + ); + state.signatures.push(signature); + // return await _ownerToAddress(owner) + } catch (error) { + throw new ContractError("ERROR_INVALID_CALLER_SIGNATURE"); + } + } + + function _validateArweaveAddress(address) { + ContractAssert( + /[a-z0-9_-]{43}/i.test(address), + "ERROR_INVALID_ARWEAVE_ADDRESS" + ); + } + + function _validatePubKeySyntax(jwk_n) { + ContractAssert( + typeof jwk_n === "string" && jwk_n?.length === 683, + "ERROR_INVALID_JWK_N_SYNTAX" + ); + } +} \ No newline at end of file diff --git a/testdata/contracts/ark.json b/testdata/contracts/ark.json new file mode 100644 index 00000000..ac17245e --- /dev/null +++ b/testdata/contracts/ark.json @@ -0,0 +1,16 @@ +{ + "containers": [], + "molecule_endpoints": { + "evm": "http://evm.molecule.sh/signer", + "ar": "http://ar.molecule.sh/ota", + "sol": "http://sol.molecule.sh/auth", + "tez": "http://tez.molecule.sh" + }, + "message_counter": 0, + "messages": { + "ar": "ark-variant--", + "evm": "ark-variant--" + }, + "signatures": [] + +} \ No newline at end of file