diff --git a/Cargo.toml b/Cargo.toml index e7680ad..4c2267a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ path = "src/lib.rs" #pyo3 = "0.19.0" pyo3 = { version = "0.19.0", features = ["extension-module", "generate-import-lib"] } #marlowe_lang = "*" -marlowe_lang = { git = "https://github.com/olofblomqvist/marlowe-rs" } \ No newline at end of file +marlowe_lang = { git = "https://github.com/olofblomqvist/marlowe-rs", features=["utils","unstable"] } \ No newline at end of file diff --git a/example.ipynb b/example.ipynb index 32736fe..bc86ff8 100644 --- a/example.ipynb +++ b/example.ipynb @@ -19,16 +19,15 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ - "import marlowepy as mm\n", - "import json\n", + "import marlowe as m\n", "\n", - "Case = mm.WrappedCase\n", - "Contract = mm.WrappedContract\n", - "Datum = mm.WrappedDatum" + "Case = m.WrappedCase\n", + "Contract = m.WrappedContract\n", + "Datum = m.WrappedDatum" ] }, { @@ -40,9 +39,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OK: When [ ] 121231233 Close\n" + ] + } + ], "source": [ "j = '{\"when\": [],\"timeout_continuation\": \"close\",\"timeout\": 121231233}'\n", "\n", @@ -65,14 +72,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"when\": [],\n", + " \"timeout_continuation\": \"close\",\n", + " \"timeout\": 123213\n", + "}\n" + ] + } + ], "source": [ - "m = 'When [] (TimeParam \"a\") Close'\n", + "dsl = 'When [] (TimeParam \"a\") Close'\n", "\n", "try:\n", - " contract = Contract.from_marlowe_dsl(m,[(\"a\",123213)])\n", + " contract = Contract.from_marlowe_dsl(dsl,[(\"a\",123213)])\n", " print(contract.as_json())\n", "except ValueError as e:\n", " print(\"ERR:\",e)\n" @@ -87,9 +106,54 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"marlowe_params\": \"8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338d\",\n", + " \"state\": {\n", + " \"accounts\": [\n", + " [\n", + " [\n", + " {\n", + " \"address\": \"addr_test1vrsdcu86j6v8yljrnh69ssm8jlg90n075hnu9pzdz2m2lzsdlspjq\"\n", + " },\n", + " {\n", + " \"token_name\": \"\",\n", + " \"currency_symbol\": \"\"\n", + " }\n", + " ],\n", + " 3000000\n", + " ]\n", + " ],\n", + " \"choices\": [],\n", + " \"boundValues\": [],\n", + " \"minTime\": 0\n", + " },\n", + " \"contract\": {\n", + " \"token\": {\n", + " \"token_name\": \"\",\n", + " \"currency_symbol\": \"\"\n", + " },\n", + " \"to\": {\n", + " \"party\": {\n", + " \"role_token\": \"WithdrawalTest1\"\n", + " }\n", + " },\n", + " \"then\": \"close\",\n", + " \"pay\": 10000000,\n", + " \"from_account\": {\n", + " \"address\": \"addr_test1vrsdcu86j6v8yljrnh69ssm8jlg90n075hnu9pzdz2m2lzsdlspjq\"\n", + " }\n", + " }\n", + "}\n" + ] + } + ], "source": [ "cbor_hex = 'd8799fd8799f581c8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338dffd8799fa1d8799fd8799fd87980d8799fd8799f581ce0dc70fa9698727e439df458436797d057cdfea5e7c2844d12b6af8affd87a80ffffd8799f4040ffff1a002dc6c0a0a000ffd87a9fd8799fd87980d8799fd8799f581ce0dc70fa9698727e439df458436797d057cdfea5e7c2844d12b6af8affd87a80ffffd87a9fd87a9f4f5769746864726177616c5465737431ffffd8799f4040ffd87a9f1a00989680ffd87980ffff'\n", "\n", @@ -104,55 +168,282 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# TODO\n", - "\n", - "Not yet decided how the API's for creating Marlowe contracts in Python should look like.." + "## Semantics" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bad contract: Generic(\"Unable to perform payment as the account ((Address \\\"addr_test1vrsdcu86j6v8yljrnh69ssm8jlg90n075hnu9pzdz2m2lzsdlspjq\\\")) does not have enough tokens ((Token \\\"\\\" \\\"\\\")). Contract attempted to send '10000000' when the account only contains '3000000'.\")\n", + "Good contract Closed\n", + "{\n", + " \"marlowe_params\": \"8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338d\",\n", + " \"state\": {\n", + " \"accounts\": [\n", + " [\n", + " [\n", + " {\n", + " \"address\": \"addr_test1qpzun0s6thsyf9qt0zyfnn50x8kpwutrv20eq8dgx9gt6tkr7cz4mu6gh005gdck67p7y9d8s8zsfgjkcdy75mrjh6jqfsjdsz\"\n", + " },\n", + " {\n", + " \"token_name\": \"\",\n", + " \"currency_symbol\": \"\"\n", + " }\n", + " ],\n", + " 2000000\n", + " ]\n", + " ],\n", + " \"choices\": [\n", + " [\n", + " {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"e.cary\"\n", + " },\n", + " \"choice_name\": \"Folio\"\n", + " },\n", + " 4\n", + " ],\n", + " [\n", + " {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"c.marlowe\"\n", + " },\n", + " \"choice_name\": \"Act\"\n", + " },\n", + " 43\n", + " ],\n", + " [\n", + " {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"f.beaumont\"\n", + " },\n", + " \"choice_name\": \"Play\"\n", + " },\n", + " 47\n", + " ]\n", + " ],\n", + " \"boundValues\": [],\n", + " \"minTime\": 1688574374000\n", + " },\n", + " \"contract\": {\n", + " \"when\": [\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"c.marlowe\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"e.cary\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"f.beaumont\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"j.lumley\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"j.webster\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"m.herbert\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " },\n", + " {\n", + " \"then\": \"MerkleizedContinuation(\\\"4fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819\\\")\",\n", + " \"case\": {\n", + " \"for_choice\": {\n", + " \"choice_owner\": {\n", + " \"role_token\": \"w.shakespeare\"\n", + " },\n", + " \"choice_name\": \"Scene\"\n", + " },\n", + " \"choose_between\": [\n", + " {\n", + " \"to\": 100,\n", + " \"from\": 0\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " ],\n", + " \"timeout_continuation\": \"close\",\n", + " \"timeout\": 1688947200000\n", + " }\n", + "}\n", + "Good contract Closed\n" + ] + } + ], "source": [ - "# Something like this?\n", + "cbor_hex = 'd8799fd8799f581c8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338dffd8799fa1d8799fd8799fd87980d8799fd8799f581ce0dc70fa9698727e439df458436797d057cdfea5e7c2844d12b6af8affd87a80ffffd8799f4040ffff1a002dc6c0a0a000ffd87a9fd8799fd87980d8799fd8799f581ce0dc70fa9698727e439df458436797d057cdfea5e7c2844d12b6af8affd87a80ffffd87a9fd87a9f4f5769746864726177616c5465737431ffffd8799f4040ffd87a9f1a00989680ffd87980ffff'\n", + "d = Datum.from_cbor_hex(cbor_hex)\n", "\n", - "contract = Contract.when(\n", - " case=[\n", - " Case.notify_on_true()\n", - " ],\n", - " contract=Contract.close(),\n", - " timeout=123\n", - ")\n", "\n", - "print(\"rust format: \",contract.as_string())\n", - "print(\"json: \",contract.as_json())\n", - "print(\"dsl: \",contract.as_marlowe_dsl())" + "try:\n", + " d.show_status()\n", + "except ValueError as e:\n", + " # This is actually an invalid contract: https://preprod.marlowescan.com/contractView?tab=state&contractId=077f1d154cbf148842902cafda55b422f4a20a5b5b79b56a8bcee504d06fdd99%231\n", + " print(\"Bad contract:\",e)\n", + "\n", + "\n", + "cbor_hex = \"d8799fd8799f581c7bb1099758c0e54996a560d225f76dc4686aa8a436588baa3d0e0588ffd8799fa1d8799fd8799fd87980d8799fd8799f581c331aaa0e2267ea6c2aece55a24e45de443235bf078f08a615ff49534ffd8799fd8799fd8799f581cf5b3b821dcf83c502b8c14161de22f60c1f5115077bba0b8baa63fb2ffffffffffd8799f4040ffff1a002dc6c0a0a000ffd87c9f9fd8799fd8799fd87a9f5350726f7669646572204e46542048616e646c65ffd87a9f5350726f7669646572204e46542048616e646c65ffd8799f4040ffd87a9f07ffffd87c9f9fd8799fd8799fd87a9f5253776170706572204e46542048616e646c65ffd87a9f5253776170706572204e46542048616e646c65ffd8799f581c8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd6144646a6564ffd87a9f05ffffd87a9fd87a9f5350726f7669646572204e46542048616e646c65ffd87a9fd87a9f5253776170706572204e46542048616e646c65ffffd8799f4040ffd87a9f07ffd87a9fd87a9f5253776170706572204e46542048616e646c65ffd87a9fd87a9f5350726f7669646572204e46542048616e646c65ffffd8799f581c8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd6144646a6564ffd87a9f05ffd87980ffffffff1b000001895056b8c0d87a9fd87a9f5350726f7669646572204e46542048616e646c65ffd87a9fd87a9f5350726f7669646572204e46542048616e646c65ffffd8799f4040ffd87a9f07ffd87980ffffffff1b000001894b305cc0d87980ffff\"\n", + "d = Datum.from_cbor_hex(cbor_hex)\n", + "print(\"Good contract\",d.show_status())\n", + "\n", + "\n", + "cbor_hex = \"d8799fd8799f581c8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338dffd8799fa1d8799fd8799fd87980d8799fd8799f581c45c9be1a5de044940b788899ce8f31ec177163629f901da83150bd2effd8799fd8799fd8799f581cc3f6055df348bbdf443716d783e215a781c504a256c349ea6c72bea4ffffffffffd8799f4040ffff1a001e8480a3d8799f45466f6c696fd87a9f46652e63617279ffff04d8799f44506c6179d87a9f4a662e626561756d6f6e74ffff182fd8799f43416374d87a9f49632e6d61726c6f7765ffff182ba01b0000018926e03070ffd87c9f9fd87a9fd87a9fd8799f455363656e65d87a9f49632e6d61726c6f7765ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f46652e63617279ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f4a662e626561756d6f6e74ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f486a2e6c756d6c6579ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f496a2e77656273746572ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f496d2e68657262657274ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffd87a9fd87a9fd8799f455363656e65d87a9f4d772e7368616b65737065617265ffff9fd8799f001864ffffff58204fa8d5688d025d64313b08de59155441b173b625b6ee04ffcfd7b19bbbc32819ffff1b000001893d191000d87980ffff\"\n", + "d = Datum.from_cbor_hex(cbor_hex)\n", + "print(d.as_json())\n", + "print(\"Good contract\",d.show_status())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TODO\n", + "\n", + "Not yet decided how the API's for creating Marlowe contracts in Python should look like.." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 12, "metadata": {}, "outputs": [ { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'marlowe'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mmarlowe\u001b[39;00m\n", - "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'marlowe'" + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "rust format: When { when: [Some(Case { case: Some(Notify { notify_if: Some(True) }), then: Some(Raw(Close)) })], timeout: Some(TimeConstant(8888888888888)), timeout_continuation: Some(Close) }\n", + "\n", + "json: {\n", + " \"when\": [\n", + " {\n", + " \"then\": \"close\",\n", + " \"case\": {\n", + " \"notify_if\": true\n", + " }\n", + " }\n", + " ],\n", + " \"timeout_continuation\": \"close\",\n", + " \"timeout\": 8888888888888\n", + "}\n", + "\n", + "dsl: When [ (Case (Notify TrueObs) Close) ] 8888888888888 Close\n", + "\n", + "-----\n", + "\n", + "status waiting for input until 8888888888888: \n", + " [Notify { obs: True, continuation: Raw(Close) }]\n" ] } ], "source": [ - "import marlowepy\n", + "# Something like this?\n", "\n", + "contract = Contract.when(\n", + " case=[\n", + " Case.notify_on_true()\n", + " ],\n", + " contract=Contract.close(),\n", + " timeout=8888888888888\n", + ")\n", "\n", + "print(\"\\nrust format: \",contract.as_string())\n", + "print(\"\\njson: \",contract.as_json())\n", + "print(\"\\ndsl: \",contract.as_marlowe_dsl())\n", + "print(\"\\n-----\")\n", "\n", - "\n" + "print(\"\\nstatus of this contract:\\n\",contract.show_status())\n" ] } ], diff --git a/src/lib.rs b/src/lib.rs index b4e156e..c90960e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,14 +3,26 @@ use pyo3::prelude::*; pub mod pytypes; #[pyfunction] -pub fn test() -> String { - String::from("HELLO FROM RUST") +pub fn decode_redeemer_from_cbor_hex(cbor_hex:&str) -> PyResult { + match marlowe_lang::extras::utils::try_decode_redeemer_input_cbor_hex(cbor_hex) { + Ok(v) => Ok(format!("{:?}",v)), + Err(e) => Err(pytypes::to_py_err(&e)), + } +} + +#[pyfunction] +pub fn try_decode_any_cbor_hex(cbor_hex:&str) -> PyResult { + match marlowe_lang::extras::utils::try_decode_any_marlowe_data(cbor_hex) { + Ok(v) => Ok(v), + Err(e) => Err(pytypes::to_py_err(&e)), + } } #[pymodule] #[pyo3(name = "marlowe")] pub fn rust(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(test, m)?)?; + m.add_function(wrap_pyfunction!(decode_redeemer_from_cbor_hex, m)?)?; + m.add_function(wrap_pyfunction!(try_decode_any_cbor_hex, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/pytypes.rs b/src/pytypes.rs index 5fa733a..012073b 100644 --- a/src/pytypes.rs +++ b/src/pytypes.rs @@ -1,6 +1,9 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +pub(crate)fn to_py_err(s:&str) -> PyErr { + PyValueError::new_err(String::from(s)) +} #[pyclass] #[derive(Clone,Debug)] @@ -33,6 +36,23 @@ impl WrappedDatum { Err(e) => Err(PyValueError::new_err(format!("did not work! {:?}",e))) } } + #[pyo3(text_signature = "($self, f)")] + pub fn show_status(&self) -> PyResult { + let instance = marlowe_lang::semantics::ContractInstance::from_datum(&self.0.clone()); + match marlowe_lang::semantics::ContractSemantics::process(&instance) { + Ok((_,state)) => match state { + marlowe_lang::semantics::MachineState::Closed => Ok("Closed".into()), + marlowe_lang::semantics::MachineState::Faulted(e) => Err(to_py_err(&e)), + marlowe_lang::semantics::MachineState::ContractHasTimedOut => Ok("timed out".into()), + marlowe_lang::semantics::MachineState::WaitingForInput { expected, timeout } => { + Ok(format!("waiting for input until {timeout}: \n {:?}", expected)) + }, + marlowe_lang::semantics::MachineState::ReadyForNextStep => todo!(), + } + Err(e) => Err(to_py_err(&format!("{:?}",e))) + } + } + } #[pyclass] @@ -123,6 +143,23 @@ impl WrappedContract { })) } + #[pyo3(text_signature = "($self, f)")] + pub fn show_status(&self) -> PyResult { + let instance = marlowe_lang::semantics::ContractInstance::new(&self.0.clone(), None); + match marlowe_lang::semantics::ContractSemantics::process(&instance) { + Ok((_,state)) => match state { + marlowe_lang::semantics::MachineState::Closed => Ok("Closed".into()), + marlowe_lang::semantics::MachineState::Faulted(e) => Err(to_py_err(&e)), + marlowe_lang::semantics::MachineState::ContractHasTimedOut => Ok("timed out".into()), + marlowe_lang::semantics::MachineState::WaitingForInput { expected, timeout } => { + Ok(format!("waiting for input until {timeout}: \n {:?}", expected)) + }, + marlowe_lang::semantics::MachineState::ReadyForNextStep => todo!(), + } + Err(e) => Err(to_py_err(&format!("{:?}",e))) + } + } + }