From fe2732b33dbb57d37812d9cde3aaeaa6e5038d28 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:30:52 +0100 Subject: [PATCH 1/2] Refactor __call__ method of ContractInstance --- examples/ram/ram.py | 17 ++-- examples/rps/rps.py | 4 +- examples/vault/vault.py | 2 +- matt/manager.py | 17 ++-- tests/test_fraud.py | 185 +++++++++++++++++++--------------------- tests/test_ram.py | 31 ++++--- tests/test_rps.py | 8 +- tests/test_vault.py | 18 ++-- 8 files changed, 141 insertions(+), 141 deletions(-) diff --git a/examples/ram/ram.py b/examples/ram/ram.py index 15765f9..95bdfff 100644 --- a/examples/ram/ram.py +++ b/examples/ram/ram.py @@ -155,9 +155,10 @@ def execute_command(input_line: str): ) ) - R_inst("withdraw", - outputs=outputs, - merkle_root=mt.root, merkle_proof=mt.prove_leaf(leaf_index)) + R_inst("withdraw", outputs=outputs)( + merkle_root=mt.root, + merkle_proof=mt.prove_leaf(leaf_index) + ) print("Done") elif action == "write": @@ -174,11 +175,11 @@ def execute_command(input_line: str): if leaf_index not in range(len(R_inst.data_expanded)): raise ValueError("Invalid leaf index") - result = R_inst("write", - merkle_root=mt.root, - new_value=new_value, - merkle_proof=mt.prove_leaf(leaf_index) - ) + result = R_inst("write")( + merkle_root=mt.root, + new_value=new_value, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(result) == 1 diff --git a/examples/rps/rps.py b/examples/rps/rps.py index 9ca40a4..53e5fbb 100644 --- a/examples/rps/rps.py +++ b/examples/rps/rps.py @@ -128,7 +128,7 @@ def start_session(self, m_a: int): print(f"Game result: {outcome}") self.env.prompt("Broadcasting adjudication transaction") - C2(outcome, m_a=m_a, m_b=m_b, r_a=r_a) + C2(outcome)(m_a=m_a, m_b=m_b, r_a=r_a) s.close() @@ -178,7 +178,7 @@ def join_session(self, m_b: int): self.env.prompt("Broadcasting Bob's move transaction") - [C2] = C("bob_move", signer=SchnorrSigner(self.priv_key), m_b=m_b) + [C2] = C("bob_move", SchnorrSigner(self.priv_key))(m_b=m_b) txid = C.spending_tx.hash print(f"Bob's move broadcasted: {m_b}. txid: {txid}") diff --git a/examples/vault/vault.py b/examples/vault/vault.py index 3414206..b84076f 100644 --- a/examples/vault/vault.py +++ b/examples/vault/vault.py @@ -248,7 +248,7 @@ def execute_command(input_line: str): if not isinstance(instance.contract, (Vault, Unvaulting)): raise ValueError("Only Vault or Unvaulting instances can be recovered") - instance("recover", out_i=0) + instance("recover")(out_i=0) elif action == "withdraw": item_idx = int(args_dict["item"]) diff --git a/matt/manager.py b/matt/manager.py index e7b460b..988a895 100644 --- a/matt/manager.py +++ b/matt/manager.py @@ -6,7 +6,7 @@ # There are no bad people here, though, so we keep it simple for now. from enum import Enum from io import BytesIO -from typing import Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, List, Optional, Tuple, Union from .argtypes import SignerType from .btctools import script @@ -102,14 +102,17 @@ def __repr__(self): value = self.funding_tx.vout[self.outpoint.n].nValue return f"{self.__class__.__name__}(contract={self.contract}, data={self.data if self.data is None else self.data.hex()}, value={value}, status={self.status}, outpoint={self.outpoint})" - def __call__(self, clause_name: str, *, signer: Optional[SchnorrSigner] = None, outputs: List[CTxOut] = [], **kwargs) -> List['ContractInstance']: - if self.manager is None: - raise ValueError("Direct invocation is only allowed after adding the instance to a ContractManager") + def __call__(self, clause_name: str, signer: Optional[SchnorrSigner] = None, outputs: List[CTxOut] = []) -> Callable[..., List['ContractInstance']]: + def callable_instance(**kwargs) -> List['ContractInstance']: + if self.manager is None: + raise ValueError("Direct invocation is only allowed after adding the instance to a ContractManager") - if self.status != ContractInstanceStatus.FUNDED: - raise ValueError("Only implemented for FUNDED instances") + if self.status != ContractInstanceStatus.FUNDED: + raise ValueError("Only implemented for FUNDED instances") - return self.manager.spend_instance(self, clause_name, kwargs, signer=signer, outputs=outputs) + return self.manager.spend_instance(self, clause_name, kwargs, signer=signer, outputs=outputs) + + return callable_instance class ContractManager: diff --git a/tests/test_fraud.py b/tests/test_fraud.py index 6a84e90..e0d1fa2 100644 --- a/tests/test_fraud.py +++ b/tests/test_fraud.py @@ -39,11 +39,10 @@ def test_leaf_reveal_alice(manager: ContractManager): ) ] - out_instances = L_inst("alice_reveal", - signer=SchnorrSigner(alice_key), - outputs=outputs, - x=x_start, - h_y_b=h_end_bob) + out_instances = L_inst("alice_reveal", SchnorrSigner(alice_key), outputs)( + x=x_start, + h_y_b=h_end_bob + ) assert len(out_instances) == 0 @@ -69,11 +68,10 @@ def test_leaf_reveal_bob(manager: ContractManager): ) ] - out_instances = L_inst("bob_reveal", - signer=SchnorrSigner(bob_key), - outputs=outputs, - x=x_start, - h_y_a=h_end_alice) + out_instances = L_inst("bob_reveal", SchnorrSigner(bob_key), outputs)( + x=x_start, + h_y_a=h_end_alice + ) assert len(out_instances) == 0 @@ -126,7 +124,7 @@ def t_node_b(i, j) -> bytes: inst = manager.fund_instance(G, AMOUNT) # Bob chooses its input - [inst] = inst('choose', signer=bob_signer, x=x) + [inst] = inst('choose', bob_signer)(x=x) assert isinstance(inst.contract, G256_S1) assert isinstance(inst.data_expanded, G256_S1.State) and inst.data_expanded.x == x @@ -135,21 +133,19 @@ def t_node_b(i, j) -> bytes: t_b = t_node_b(0, n - 1) # trace root according to Bob # Alice reveals her answer - [inst] = inst('reveal', signer=alice_signer, - x=x, - y=y, - t_a=t_a) + [inst] = inst('reveal', alice_signer)(x=x, y=y, t_a=t_a) assert isinstance(inst.contract, G256_S2) assert inst.data_expanded == G256_S2.State(t_a=t_a, x=x, y=y) # Bob disagrees and starts the challenge - [inst] = inst('start_challenge', signer=bob_signer, - t_a=t_a, - x=x, - y=y, - z=z, - t_b=t_b) + [inst] = inst('start_challenge', bob_signer)( + t_a=t_a, + x=x, + y=y, + z=z, + t_b=t_b + ) # inst now represents a step in the bisection protocol corresponding to the root of the computation @@ -157,34 +153,34 @@ def t_node_b(i, j) -> bytes: assert inst.contract.i == 0 and inst.contract.j == 7 i, j = inst.contract.i, inst.contract.j m = (j - i + 1) // 2 - [inst] = inst('alice_reveal', signer=alice_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j) - ) + [inst] = inst('alice_reveal', alice_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j) + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Alice)")) assert isinstance(inst.contract, Bisect_2) assert inst.contract.i == 0 and inst.contract.j == 7 - [inst] = inst('bob_reveal_right', signer=bob_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j), - h_mid_b=h_b[i + m], - trace_left_b=t_node_b(i, i + m - 1), - trace_right_b=t_node_b(i + m, j), - ) + [inst] = inst('bob_reveal_right', bob_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j), + h_mid_b=h_b[i + m], + trace_left_b=t_node_b(i, i + m - 1), + trace_right_b=t_node_b(i + m, j), + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Bob, right child)")) assert isinstance(inst.contract, Bisect_1) @@ -193,34 +189,34 @@ def t_node_b(i, j) -> bytes: assert i == 4 and j == 7 # Bisection repeats on the node covering from index 4 to index 7 - [inst] = inst('alice_reveal', signer=alice_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j) - ) + [inst] = inst('alice_reveal', alice_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j) + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Alice)")) assert isinstance(inst.contract, Bisect_2) assert inst.contract.i == 4 and inst.contract.j == 7 - [inst] = inst('bob_reveal_left', signer=bob_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j), - h_mid_b=h_b[i + m], - trace_left_b=t_node_b(i, i + m - 1), - trace_right_b=t_node_b(i + m, j), - ) + [inst] = inst('bob_reveal_left', bob_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j), + h_mid_b=h_b[i + m], + trace_left_b=t_node_b(i, i + m - 1), + trace_right_b=t_node_b(i + m, j), + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Bob, left child)")) assert isinstance(inst.contract, Bisect_1) @@ -230,34 +226,34 @@ def t_node_b(i, j) -> bytes: # Bisection repeats on the node covering from index 4 to index 5 (last bisection step) - [inst] = inst('alice_reveal', signer=alice_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j) - ) + [inst] = inst('alice_reveal', alice_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j) + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Alice)")) assert isinstance(inst.contract, Bisect_2) assert inst.contract.i == 4 and inst.contract.j == 5 - [inst] = inst('bob_reveal_right', signer=bob_signer, - h_start=h_a[i], - h_end_a=h_a[j + 1], - h_end_b=h_b[j + 1], - trace_a=t_node_a(i, j), - trace_b=t_node_b(i, j), - h_mid_a=h_a[i + m], - trace_left_a=t_node_a(i, i + m - 1), - trace_right_a=t_node_a(i + m, j), - h_mid_b=h_b[i + m], - trace_left_b=t_node_b(i, i + m - 1), - trace_right_b=t_node_b(i + m, j), - ) + [inst] = inst('bob_reveal_right', bob_signer)( + h_start=h_a[i], + h_end_a=h_a[j + 1], + h_end_b=h_b[j + 1], + trace_a=t_node_a(i, j), + trace_b=t_node_b(i, j), + h_mid_a=h_a[i + m], + trace_left_a=t_node_a(i, i + m - 1), + trace_right_a=t_node_a(i + m, j), + h_mid_b=h_b[i + m], + trace_left_b=t_node_b(i, i + m - 1), + trace_right_b=t_node_b(i + m, j), + ) report.write("Fraud proof", format_tx_markdown(inst.funding_tx, "Bisection (Bob, right child)")) # We reached a leaf. Only who was doubling correctly can withdraw @@ -272,11 +268,10 @@ def t_node_b(i, j) -> bytes: scriptPubKey=bytes([0, 0x20, *[0x42]*32]) ) ] - out_instances = inst("bob_reveal", - signer=SchnorrSigner(bob_key), - outputs=outputs, - x=bob_trace[5], - h_y_a=h_a[6]) + out_instances = inst("bob_reveal", bob_signer, outputs)( + x=bob_trace[5], + h_y_a=h_a[6] + ) assert len(out_instances) == 0 diff --git a/tests/test_ram.py b/tests/test_ram.py index 116bd7e..fe19ac1 100644 --- a/tests/test_ram.py +++ b/tests/test_ram.py @@ -9,7 +9,7 @@ AMOUNT = 20_000 -def test_withdraw(rpc, manager: ContractManager): +def test_withdraw(manager: ContractManager): # tests the "withdraw" clause, that allows spending anywhere as long # as a valid Merkle proof is provided for size in [8, 16]: @@ -27,11 +27,10 @@ def test_withdraw(rpc, manager: ContractManager): ) ] - out_instances = R_inst("withdraw", - outputs=outputs, - merkle_root=mt.root, - merkle_proof=mt.prove_leaf(leaf_index) - ) + out_instances = R_inst("withdraw", outputs=outputs)( + merkle_root=mt.root, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(out_instances) == 0 @@ -48,11 +47,11 @@ def test_write(manager: ContractManager): R = RAM(len(data)) R_inst = manager.fund_instance(R, AMOUNT, data=R.State(data)) - out_instances = R_inst("write", - merkle_root=mt.root, - new_value=new_value, - merkle_proof=mt.prove_leaf(leaf_index) - ) + out_instances = R_inst("write")( + merkle_root=mt.root, + new_value=new_value, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(out_instances) == 1 @@ -77,11 +76,11 @@ def test_write_loop(manager: ContractManager): leaf_index = i % size new_value = sha256((100 + i).to_bytes(1, byteorder='little')) - out_instances = R_inst("write", - merkle_root=MerkleTree(data).root, - new_value=new_value, - merkle_proof=MerkleTree(data).prove_leaf(leaf_index) - ) + out_instances = R_inst("write")( + merkle_root=MerkleTree(data).root, + new_value=new_value, + merkle_proof=MerkleTree(data).prove_leaf(leaf_index) + ) assert len(out_instances) == 1 diff --git a/tests/test_rps.py b/tests/test_rps.py index 72020fc..a96c0fd 100644 --- a/tests/test_rps.py +++ b/tests/test_rps.py @@ -39,15 +39,15 @@ def test_rps(manager: ContractManager): # Bob's move m_b = moves["paper"] - [S1_inst] = S0_inst("bob_move", signer=bob_signer, m_b=m_b) + [S1_inst] = S0_inst("bob_move", bob_signer)(m_b=m_b) # cheating attempt with pytest.raises(JSONRPCException, match='Script failed an OP_EQUALVERIFY operation'): - S1_inst("alice_wins", m_a=m_a, m_b=m_b, r_a=r_a) + S1_inst("alice_wins")(m_a=m_a, m_b=m_b, r_a=r_a) # cheat a bit less with pytest.raises(JSONRPCException, match='Script failed an OP_EQUALVERIFY operation'): - S1_inst("tie", m_a=m_a, m_b=m_b, r_a=r_a) + S1_inst("tie")(m_a=m_a, m_b=m_b, r_a=r_a) # correct adjudication - S1_inst("bob_wins", m_a=m_a, m_b=m_b, r_a=r_a) + S1_inst("bob_wins")(m_a=m_a, m_b=m_b, r_a=r_a) diff --git a/tests/test_vault.py b/tests/test_vault.py index f3bc65f..2e44f8a 100644 --- a/tests/test_vault.py +++ b/tests/test_vault.py @@ -53,7 +53,7 @@ def test_vault_recover(vault_specs: VaultSpecs, manager: ContractManager, report V_inst = manager.fund_instance(vault_contract, amount) - out_instances = V_inst("recover", out_i=0) + out_instances = V_inst("recover")(out_i=0) out: CTxOut = V_inst.spending_tx.vout[0] @@ -69,8 +69,6 @@ def test_vault_recover(vault_specs: VaultSpecs, manager: ContractManager, report def test_vault_trigger_and_recover(vault_specs: VaultSpecs, manager: ContractManager, report): vault_description, vault_contract = vault_specs - signer = SchnorrSigner(unvault_priv_key) - amount = 4999990000 V_inst = manager.fund_instance(vault_contract, amount) @@ -81,12 +79,14 @@ def test_vault_trigger_and_recover(vault_specs: VaultSpecs, manager: ContractMan ("bcrt1q6vqduw24yjjll6nfkxlfy2twwt52w58tnvnd46", 4999990000), ], nSequence=locktime) - [U_inst] = V_inst("trigger", signer=signer, - out_i=0, ctv_hash=ctv_tmpl.get_standard_template_hash(0)) + [U_inst] = V_inst("trigger", signer=SchnorrSigner(unvault_priv_key))( + out_i=0, + ctv_hash=ctv_tmpl.get_standard_template_hash(0) + ) report.write(vault_description, format_tx_markdown(V_inst.spending_tx, "Trigger")) - out_instances = U_inst("recover", out_i=0) + out_instances = U_inst("recover")(out_i=0) assert len(out_instances) == 0 @@ -109,8 +109,10 @@ def test_vault_trigger_and_withdraw(vault_specs: VaultSpecs, rpc: AuthServicePro ("bcrt1q6vqduw24yjjll6nfkxlfy2twwt52w58tnvnd46", 1666663334), ], nSequence=locktime) - [U_inst] = V_inst("trigger", signer=signer, - out_i=0, ctv_hash=ctv_tmpl.get_standard_template_hash(0)) + [U_inst] = V_inst("trigger", signer=signer)( + out_i=0, + ctv_hash=ctv_tmpl.get_standard_template_hash(0) + ) spend_tx, _ = manager.get_spend_tx( (U_inst, "withdraw", {"ctv_hash": ctv_tmpl.get_standard_template_hash(0)}) From 642eba2861f37ba7618e64cbfd84dcdf459a6bb8 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:34:38 +0100 Subject: [PATCH 2/2] Fix interactive Ram example; must declare the data when funding the instance --- examples/ram/ram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ram/ram.py b/examples/ram/ram.py index 95bdfff..3098b81 100644 --- a/examples/ram/ram.py +++ b/examples/ram/ram.py @@ -190,7 +190,8 @@ def execute_command(input_line: str): amount = int(args_dict["amount"]) content = [sha256(i.to_bytes(1, byteorder='little')) for i in range(8)] - R_inst = manager.fund_instance(RAM(len(content)), amount, data=MerkleTree(content).root) + R = RAM(len(content)) + R_inst = manager.fund_instance(R, amount, data=R.State(content)) R_inst.data_expanded = content