diff --git a/docs/contracts.md b/docs/contracts.md
index 558bdac..1c1b79c 100644
--- a/docs/contracts.md
+++ b/docs/contracts.md
@@ -10,11 +10,13 @@ See [checkcontractverify.md](checkcontractverify.md) for the semantics of `OP_CH
## Contracts, programs and clauses
-The internal pubkey (or the _naked_ pubkey for an augmented P2TR), together with the taptree, constitutes the ___program___ of the contract, which encodes all the spending conditions of the contract.
+The internal pubkey (the _naked_ pubkey), together with the taptree, constitutes the ___program___ of the contract, which encodes all the spending conditions of the contract.
+
+All contracts in this framework are _augmented_ P2TR contracts, meaning they can have embedded data. For stateless contracts (those without state), the embedded data is simply the empty buffer `b''`, which by the semantics of `tweak_embed_data` leaves the internal pubkey unchanged. Note that embedding the empty buffer b'' (or the number 0, which encodes as the empty buffer in Script) leaves the internal pubkey unchanged per tweak_embed_data semantics, and can therefore be used for any contract that does not actually need to embed any data.
An actual UTXO whose `scriptPubKey` is a program, possibly with some specified embedded _data_, is a ___contract instance___.
-We call ___clause___ each of the spending conditions in the taptree of. Each clause might also specify the state transition rules, by defining the program of one or more of the outputs.
+We call ___clause___ each of the spending conditions in the taptree. Each clause might also specify the state transition rules, by defining the program of one or more of the outputs.
The keypath, if not a NUMS (Nothing-Up-My-Sleeve) point, can also be considered an additional special clause with no condition on the outputs.
### Merklelized data
@@ -109,10 +111,6 @@ The spending condition can be any predicate that can be expressed in Script, wit
_Note_: this ignores the technical details of how to encode/decode the state variables to/from a single hash; that is an implementation detail that can safely be left out when discussing the semantic of a smart contract.
-### Default contract
-
-The contract `P2TR{pk}` is equal to the output script descriptor `tr(pk)`.
-
### Example: Vault
With the above conventions, we can model the Vault contract drawn above as follows:
diff --git a/docs/matt.md b/docs/matt.md
index 822742f..1dbe37c 100644
--- a/docs/matt.md
+++ b/docs/matt.md
@@ -15,12 +15,12 @@ A script that adds such restriction is called a covenant, and that is not possib
The core idea in MATT is to introduce the following capability to be accessible within Script:
- force an output to have a certain Script (and their amounts)
-- attach a piece of data to an output
+- optionally, attach a piece of data to an output
- read the data of the current input (or another one)
The first is common to many other covenant proposals, for example [OP_CHECKTEMPLATEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0119.mediawiki) is a long-discussed proposal that can constrain all the outputs at the same time.
-The part relative to the data is more specific: this data can be as short as a 32-byte hash, but the key is that the data of an output is not decided when the UTXO is first created, but it is dynamically computed in Script (and therefore it can depend on "parameters" that are passed by the spender). This is extremely powerful, as it allows to create some sort of "state machines" where the execution can decide:
+The part relative to the data is more specific: this data can be an arbitrary buffer. The key is that the data of an output is not decided when the UTXO is first created, but it is dynamically computed in Script (and therefore it can depend on "parameters" that are passed by the spender). This is extremely powerful, as it allows to create some sort of "state machines" where the execution can decide:
- what is the next "state" of the state machine (by constraining the Script of the outputs)
- what is the "data" attached to the next state
diff --git a/examples/game256/game256_contracts.py b/examples/game256/game256_contracts.py
index bfc724b..f8a7096 100644
--- a/examples/game256/game256_contracts.py
+++ b/examples/game256/game256_contracts.py
@@ -5,7 +5,7 @@
from matt.argtypes import BytesType, IntType, SignerType
from matt.btctools.common import sha256
from matt.btctools.script import OP_ADD, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_FROMALTSTACK, OP_NOT, OP_PICK, OP_ROT, OP_SHA256, OP_SWAP, OP_TOALTSTACK, OP_VERIFY, CScript
-from matt.contracts import ClauseOutput, StandardClause, StandardAugmentedP2TR, StandardP2TR, ContractState
+from matt.contracts import ClauseOutput, StandardClause, StandardAugmentedP2TR, ContractState
from matt.hub.fraud import Bisect_1, Computer, Leaf
from matt.merkle import MerkleTree
from matt.script_helpers import check_input_contract, check_output_contract, dup, merkle_root, older
@@ -19,7 +19,9 @@
# Do we need "clause" algebra?
-class G256_S0(StandardP2TR):
+class G256_S0(StandardAugmentedP2TR):
+ State = None # Stateless contract
+
def __init__(self, alice_pk: bytes, bob_pk: bytes, forfait_timeout: int = 10):
self.alice_pk = alice_pk
self.bob_pk = bob_pk
diff --git a/examples/rps/rps_contracts.py b/examples/rps/rps_contracts.py
index 4da95a3..9988e22 100644
--- a/examples/rps/rps_contracts.py
+++ b/examples/rps/rps_contracts.py
@@ -6,7 +6,7 @@
from matt.btctools.messages import sha256
from matt.btctools import script
from matt.btctools.script import OP_ADD, OP_CAT, OP_CHECKSIG, OP_CHECKTEMPLATEVERIFY, OP_DUP, OP_ENDIF, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_IF, OP_LESSTHAN, OP_OVER, OP_SHA256, OP_SUB, OP_SWAP, OP_TOALTSTACK, OP_VERIFY, OP_WITHIN, CScript
-from matt.contracts import P2TR, ClauseOutput, StandardClause, StandardP2TR, StandardAugmentedP2TR, ContractState
+from matt.contracts import ClauseOutput, SimpleP2TR, StandardClause, StandardAugmentedP2TR, ContractState
from matt.script_helpers import check_input_contract, check_output_contract
from matt.utils import encode_wit_element, make_ctv_template
@@ -49,7 +49,9 @@ def calculate_hash(move: int, r: bytes) -> bytes:
# - c_a
# spending conditions:
# - bob_pk (m_b) => RPSGameS1[m_b]
-class RPSGameS0(StandardP2TR):
+class RPSGameS0(StandardAugmentedP2TR):
+ State = None # Stateless contract
+
def __init__(self, alice_pk: bytes, bob_pk: bytes, c_a: bytes, stake: int = DEFAULT_STAKE):
assert len(alice_pk) == 32 and len(bob_pk) == 32 and len(c_a) == 32
@@ -160,8 +162,8 @@ def make_script(diff: int, ctv_hash: bytes):
OP_CHECKTEMPLATEVERIFY
])
- alice_spk = P2TR(self.alice_pk, []).get_tr_info().scriptPubKey
- bob_spk = P2TR(self.bob_pk, []).get_tr_info().scriptPubKey
+ alice_spk = SimpleP2TR(self.alice_pk).get_tr_info(b'').scriptPubKey
+ bob_spk = SimpleP2TR(self.bob_pk).get_tr_info(b'').scriptPubKey
tmpl_alice_wins = make_ctv_template([(alice_spk, 2*self.stake)])
tmpl_bob_wins = make_ctv_template([(bob_spk, 2*self.stake)])
diff --git a/examples/vault/minivault_contracts.py b/examples/vault/minivault_contracts.py
index 2806de6..4021140 100644
--- a/examples/vault/minivault_contracts.py
+++ b/examples/vault/minivault_contracts.py
@@ -4,11 +4,13 @@
from matt import CCV_FLAG_DEDUCT_OUTPUT_AMOUNT, NUMS_KEY
from matt.argtypes import BytesType, IntType, SignerType
from matt.btctools.script import OP_CHECKCONTRACTVERIFY, OP_CHECKSIG, OP_DUP, OP_PICK, OP_SWAP, OP_TRUE, CScript
-from matt.contracts import ClauseOutput, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardClause, StandardP2TR, StandardAugmentedP2TR, ContractState
+from matt.contracts import ClauseOutput, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardClause, StandardAugmentedP2TR, ContractState
from matt.script_helpers import check_input_contract, older
-class Vault(StandardP2TR):
+class Vault(StandardAugmentedP2TR):
+ State = None # Stateless contract
+
def __init__(self, alternate_pk: Optional[bytes], spend_delay: int, recover_pk: bytes, unvault_pk: bytes, *, has_partial_revault=True, has_early_recover=True):
assert (alternate_pk is None or len(alternate_pk) == 32) and len(recover_pk) == 32 and len(unvault_pk) == 32
diff --git a/examples/vault/vault_contracts.py b/examples/vault/vault_contracts.py
index bc7a0f6..9c8848e 100644
--- a/examples/vault/vault_contracts.py
+++ b/examples/vault/vault_contracts.py
@@ -4,11 +4,13 @@
from matt import CCV_FLAG_DEDUCT_OUTPUT_AMOUNT, NUMS_KEY
from matt.argtypes import BytesType, IntType, SignerType
from matt.btctools.script import OP_CHECKCONTRACTVERIFY, OP_CHECKSIG, OP_CHECKTEMPLATEVERIFY, OP_DUP, OP_SWAP, OP_TRUE, CScript
-from matt.contracts import ClauseOutput, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardClause, StandardP2TR, StandardAugmentedP2TR, ContractState
+from matt.contracts import ClauseOutput, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardClause, StandardAugmentedP2TR, ContractState
from matt.script_helpers import check_input_contract, older
-class Vault(StandardP2TR):
+class Vault(StandardAugmentedP2TR):
+ State = None # Stateless contract
+
def __init__(self, alternate_pk: Optional[bytes], spend_delay: int, recover_pk: bytes, unvault_pk: bytes, *, has_partial_revault=True, has_early_recover=True):
assert (alternate_pk is None or len(alternate_pk) == 32) and len(recover_pk) == 32 and len(unvault_pk) == 32
diff --git a/pyproject.toml b/pyproject.toml
index b32c17d..b8500ac 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,6 @@ max-line-length = 120
[tool.pytest.ini_options]
python_files = '*.py'
-pythonpath = [ 'src' ]
testpaths = [ 'tests' ]
diff --git a/src/matt/contracts.py b/src/matt/contracts.py
index abf4f3f..7a8ca35 100644
--- a/src/matt/contracts.py
+++ b/src/matt/contracts.py
@@ -56,6 +56,25 @@ def encoder_script(*args, **kwargs) -> CScript:
pass
+@dataclass
+class EmptyState(ContractState):
+ """
+ Represents the empty state for stateless contracts.
+ When used, the data tweak is empty (b''), which by tweak_embed_data semantics leaves the key unchanged.
+ """
+
+ def encode(self) -> bytes:
+ return b''
+
+ @staticmethod
+ def encoder_script() -> CScript:
+ return CScript([])
+
+
+# Singleton instance for stateless contracts
+EMPTY_STATE = EmptyState()
+
+
class ClauseOutputAmountBehaviour(Enum):
"""
Defines the semantic of a clause with respect to an output's amount, where the output is enforced by OP_CHECKCONTRACTVERIFY.
@@ -74,21 +93,21 @@ class ClauseOutput:
contract instance.
This class encapsulates the details necessary to construct an output from a contract clause, including
- the output index, the contract of that output, the internal state of the output (or None if the output is
- not augmented), and the amount semantic for that output.
+ the output index, the contract of that output, the internal state of the output, and the amount semantic
+ for that output.
Attributes:
n (Optional[int]): The index of the output. A value of -1 implies that the output's index equals
the current input's index.
next_contract (AbstractContract): The contract of this output.
- next_state (Optional[ContractState]): The state data for the next contract instance. This is
- compulsory for augmented contracts, and it must be None otherwise.
+ next_state (ContractState): The state data for the next contract instance. Defaults to EMPTY_STATE
+ for stateless contracts.
next_amount (ClauseOutputAmountBehaviour): Determines the semantic of the output amount.
"""
n: int
- next_contract: AbstractContract # only StandardP2TR and StandardAugmentedP2TR are supported so far
- next_state: Optional[ContractState] = None # only meaningful if the contract is augmented
+ next_contract: AbstractContract
+ next_state: 'ContractState' = EMPTY_STATE
next_amount: ClauseOutputAmountBehaviour = ClauseOutputAmountBehaviour.PRESERVE_OUTPUT
def __repr__(self):
@@ -126,14 +145,14 @@ def stack_elements_from_args(self, args: dict) -> List[bytes]:
pass
@abstractmethod
- def next_outputs(self, args: dict, state: Union[ContractState, None] = None) -> List[ClauseOutput]:
+ def next_outputs(self, args: dict, state: ContractState = EMPTY_STATE) -> List[ClauseOutput]:
"""
Determines the resulting outputs of the clause, based on the arguments of the clause,
and the contract state.
Parameters:
args (Dict): The arguments of the claise.
- state (Union[ContractState, None], optional): The current state of the contract, if applicable.
+ state (ContractState): The current state of the contract. Defaults to EMPTY_STATE for stateless contracts.
Returns:
List[ClauseOutput]: The outputs generated by the clause.
@@ -178,8 +197,8 @@ def __init__(self, name: str, script: CScript, arg_specs: List[Tuple[str, ArgTyp
script (CScript): The Script associated with the clause.
arg_specs (List[Tuple[str, ArgType]]): Specifications for the clause's arguments.
next_outputs_fn (Optional[Callable]): An optional function to compute the next outputs based on
- the clause's arguments and the current contract state. The function can eiter return a list
- of clause outputs, or a CTV template.
+ the clause's arguments and the current contract state (defaults to EMPTY_STATE for stateless).
+ The function can either return a list of clause outputs, or a CTV template.
"""
super().__init__(name, script)
@@ -187,13 +206,13 @@ def __init__(self, name: str, script: CScript, arg_specs: List[Tuple[str, ArgTyp
self.next_outputs_fn = next_outputs_fn
- def next_outputs(self, args: dict, state: Optional[ContractState]) -> Union[List[ClauseOutput], CTransaction]:
+ def next_outputs(self, args: dict, state: ContractState = EMPTY_STATE) -> Union[List[ClauseOutput], CTransaction]:
"""
Computes the outputs produced by the clause, based on the clause arguments and the contract state.
Parameters:
args (dict): The arguments to the clause.
- state (Optional[ContractState]): The current state of the contract, if relevant.
+ state (ContractState): The current state of the contract. Defaults to EMPTY_STATE for stateless contracts.
Returns:
Union[List[ClauseOutput], CTransaction]: The outputs generated by the clause, or a CTV template.
@@ -290,28 +309,6 @@ def __repr__(self) -> str:
TaptreeDescription = List['TaptreeDescription']
-class P2TR(AbstractContract):
- """
- A class representing a Pay-to-Taproot script.
- """
-
- def __init__(self, internal_pubkey: bytes, scripts: TaptreeDescription):
- assert len(internal_pubkey) == 32
-
- self.internal_pubkey = internal_pubkey
- self.scripts = scripts
- self.tr_info = script.taproot_construct(internal_pubkey, scripts)
-
- def get_tr_info(self) -> TaprootInfo:
- return self.tr_info
-
- def get_address(self) -> str:
- return encode_segwit_address("bcrt", 1, bytes(self.get_tr_info().scriptPubKey)[2:])
-
- def __repr__(self):
- return f"{self.__class__.__name__}(internal_pubkey={self.internal_pubkey.hex()})"
-
-
class AugmentedP2TR(AbstractContract):
"""
An abstract class representing a Pay-to-Taproot script with some embedded data.
@@ -340,6 +337,28 @@ def __repr__(self):
return f"{self.__class__.__name__}(naked_internal_pubkey={self.naked_internal_pubkey.hex()}. Contracts's data: {self.data})"
+class SimpleP2TR(AugmentedP2TR):
+ """
+ A simple Pay-to-Taproot contract with an optional taptree.
+ Useful for creating basic P2TR outputs without defining a full contract class.
+ """
+
+ def __init__(self, pubkey: bytes, taptree: TaptreeDescription = []):
+ """
+ Initializes a SimpleP2TR contract.
+
+ Parameters:
+ pubkey (bytes): The 32-byte public key.
+ taptree (TaptreeDescription): The taptree description. Defaults to [] for keypath-only spend.
+ """
+ assert len(pubkey) == 32
+ super().__init__(pubkey)
+ self.taptree = taptree
+
+ def get_scripts(self) -> TaptreeDescription:
+ return self.taptree
+
+
StandardTaptreeDescription = Union[StandardClause, List['StandardTaptreeDescription']]
@@ -365,40 +384,15 @@ def _flatten_standard_taptree_description(std_tree: StandardTaptreeDescription)
return [std_tree]
-class StandardP2TR(P2TR):
+class StandardAugmentedP2TR(AugmentedP2TR, ABC):
"""
- A StandardP2TR where all the transitions are given by a StandardClause.
+ An AugmentedP2TR where all the transitions are given by a StandardClause.
"""
- def __init__(self, internal_pubkey: bytes, standard_taptree: StandardTaptreeDescription):
- super().__init__(internal_pubkey, _normalize_standard_taptree_description(standard_taptree))
- self.standard_taptree = standard_taptree
- self.clauses = _flatten_standard_taptree_description(standard_taptree)
- self._clauses_dict = {clause.name: clause for clause in self.clauses}
-
- def get_scripts(self) -> List[Tuple[str, CScript]]:
- return list(map(lambda clause: (clause.name, clause.script), self.clauses))
-
- def decode_wit_stack(self, stack_elems: List[bytes]) -> Tuple[str, dict]:
- leaf_hash = stack_elems[-2]
-
- clause_name = None
- for clause in self.clauses:
- if leaf_hash == self.get_tr_info().leaves[clause.name].script:
- clause_name = clause.name
- break
- if clause_name is None:
- raise ValueError("Clause not found")
-
- return clause_name, self._clauses_dict[clause_name].args_from_stack_elements(stack_elems[:-2])
-
- def __repr__(self):
- return f"{self.__class__.__name__}(internal_pubkey={self.internal_pubkey.hex()})"
-
-
-class StandardAugmentedP2TR(AugmentedP2TR, ABC):
+ State: Optional[Type[ContractState]] = None
"""
- An AugmentedP2TR where all the transitions are given by a StandardClause.
+ The State class for this contract, or None for stateless contracts.
+ Subclasses with state should override this with their ContractState subclass.
"""
def __init__(self, naked_internal_pubkey: bytes, standard_taptree: StandardTaptreeDescription):
@@ -425,8 +419,3 @@ def decode_wit_stack(self, data: bytes, stack_elems: List[bytes]) -> Tuple[str,
def __repr__(self):
return f"{self.__class__.__name__}(naked_internal_pubkey={self.naked_internal_pubkey.hex()})"
-
- @property
- @abstractmethod
- def State() -> Type[ContractState]:
- pass
diff --git a/src/matt/manager.py b/src/matt/manager.py
index 1a6a9ae..c26a5a4 100644
--- a/src/matt/manager.py
+++ b/src/matt/manager.py
@@ -17,8 +17,6 @@
from io import BytesIO
from typing import Callable, Dict, Generic, List, Optional, Tuple, TypeVar, Union
-from typing_extensions import TypeGuard
-
from .argtypes import SignerType
from .btctools import script
from .btctools.auth_proxy import AuthServiceProxy
@@ -26,7 +24,7 @@
from .btctools.messages import COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut
from .btctools.script import TaprootInfo
from .btctools.segwit_addr import encode_segwit_address
-from .contracts import P2TR, AugmentedP2TR, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardAugmentedP2TR, StandardP2TR, ContractState
+from .contracts import AugmentedP2TR, ClauseOutputAmountBehaviour, OpaqueP2TR, StandardAugmentedP2TR, ContractState, EMPTY_STATE
from .utils import wait_for_output, wait_for_spending_tx
@@ -66,11 +64,11 @@ def sign(self, msg: bytes, pubkey: bytes) -> Optional[bytes]:
class ContractInstanceStatus(Enum):
"""Represents each of the possible conditions of a ContractInstance lifetime"""
ABSTRACT = 0 # Before being funded, no attached UTXO, nor any defined state.
- FUNDED = 1 # Funded, attached to a specific UTXO; if augmented, its state is defined.
+ FUNDED = 1 # Funded, attached to a specific UTXO; state is defined.
SPENT = 2 # Already spent
-ContractT = TypeVar('ContractT', bound=Union[StandardP2TR, StandardAugmentedP2TR])
+ContractT = TypeVar('ContractT', bound=StandardAugmentedP2TR)
class ContractInstance(Generic[ContractT]):
@@ -85,13 +83,12 @@ def __init__(self, contract: ContractT):
Initializes a new ContractInstance with the given contract template.
Parameters:
- contract (Union[StandardP2TR, StandardAugmentedP2TR]): The contract template for this instance,
- which can either be a standard or augmented Pay-to-Taproot contract.
+ contract (StandardAugmentedP2TR): The contract template for this instance.
"""
self.contract = contract
- self.data: Optional[bytes] = None
- self.data_expanded: Optional[ContractState] = None # TODO: figure out a good API for this
+ self.data: bytes = b''
+ self.data_expanded: Optional[ContractState] = None
self.manager: Optional[ContractManager] = None
@@ -109,31 +106,14 @@ def __init__(self, contract: ContractT):
# the new instances produced by spending this instance
self.next: Optional[List[ContractInstance]] = None
- def is_augmented(self) -> TypeGuard['ContractInstance[StandardAugmentedP2TR]']:
- """
- Checks if the Contract contained in this instance is augmented.
-
- Returns:
- bool: True if the contract is augmented, False otherwise.
- """
- return isinstance(self.contract, StandardAugmentedP2TR)
-
def get_tr_info(self) -> TaprootInfo:
"""
Returns the associated TaprootInfo object.
Returns:
TaprootInfo: An object with info about the taptree.
-
- Raises:
- ValueError: If the contract is augmented but no data is set for the instance.
"""
- if not self.is_augmented():
- return self.contract.get_tr_info()
- else:
- if self.data is None:
- raise ValueError("Cannot generate address for augmented instance before setting the data")
- return self.contract.get_tr_info(self.data)
+ return self.contract.get_tr_info(self.data)
def get_address(self) -> str:
"""
@@ -173,12 +153,7 @@ def decode_wit_stack(self, stack_elems: List[bytes]) -> Tuple[str, dict]:
Returns:
Tuple[str, dict]: A tuple containing the name of the clause used and a dictionary of the arguments provided to the clause.
"""
- if self.is_augmented():
- assert self.data is not None
-
- return self.contract.decode_wit_stack(self.data, stack_elems)
- else:
- return self.contract.decode_wit_stack(stack_elems)
+ return self.contract.decode_wit_stack(self.data, stack_elems)
def __repr__(self):
value = None
@@ -297,12 +272,7 @@ def wait_for_outpoint(self, instance: ContractInstance, txid: Optional[str] = No
"""
self._check_instance(instance, exp_statuses=ContractInstanceStatus.ABSTRACT)
- if instance.is_augmented():
- if instance.data is None:
- raise ValueError("Data not set in instance")
- scriptPubKey = instance.contract.get_tr_info(instance.data).scriptPubKey
- else:
- scriptPubKey = instance.contract.get_tr_info().scriptPubKey
+ scriptPubKey = instance.contract.get_tr_info(instance.data).scriptPubKey
if self.mine_automatically:
self._mine_blocks(1)
@@ -389,11 +359,9 @@ def get_spend_tx(
ccv_amount = instance.funding_tx.vout[instance.outpoint.n].nValue
for clause_output in next_outputs:
out_contract = clause_output.next_contract
- if isinstance(out_contract, (P2TR, OpaqueP2TR)):
+ if isinstance(out_contract, OpaqueP2TR):
out_scriptPubKey = out_contract.get_tr_info().scriptPubKey
elif isinstance(out_contract, AugmentedP2TR):
- if clause_output.next_state is None:
- raise ValueError("Missing data for augmented output")
out_scriptPubKey = out_contract.get_tr_info(clause_output.next_state.encode()).scriptPubKey
else:
raise ValueError("Unsupported contract type")
@@ -599,12 +567,9 @@ def wait_for_spend(self, instances: Union[ContractInstance, List[ContractInstanc
out_contract = clause_output.next_contract
new_instance = ContractInstance(out_contract)
- if isinstance(out_contract, (StandardP2TR, StandardAugmentedP2TR)):
- if isinstance(out_contract, StandardAugmentedP2TR):
- if clause_output.next_state is None:
- raise ValueError("Missing data for augmented output")
- new_instance.data = clause_output.next_state.encode()
- new_instance.data_expanded = clause_output.next_state
+ if isinstance(out_contract, StandardAugmentedP2TR):
+ new_instance.data = clause_output.next_state.encode()
+ new_instance.data_expanded = clause_output.next_state
new_instance.last_height = instance.last_height
@@ -615,7 +580,7 @@ def wait_for_spend(self, instances: Union[ContractInstance, List[ContractInstanc
out_contracts[output_index] = new_instance
next_instances.append(new_instance)
- elif isinstance(out_contract, (P2TR, OpaqueP2TR)):
+ elif isinstance(out_contract, OpaqueP2TR):
continue # nothing to do, will not track this output
else:
raise ValueError("Unsupported contract type")
@@ -626,41 +591,29 @@ def wait_for_spend(self, instances: Union[ContractInstance, List[ContractInstanc
self.add_instance(instance)
return result
- def fund_instance(self, contract: ContractT, amount: int, data: Optional[ContractState] = None) -> ContractInstance[ContractT]:
+ def fund_instance(self, contract: ContractT, amount: int, data: ContractState = EMPTY_STATE) -> ContractInstance[ContractT]:
"""
Creates a new contract instance from a specified contract template, funds it with a specified amount of satoshis,
and adds it to the manager.
Parameters:
- contract (Union[StandardP2TR, StandardAugmentedP2TR]): The contract template to create an instance of. This can be
- either a standard P2TR contract or an augmented P2TR contract with additional data capabilities.
+ contract (StandardAugmentedP2TR): The contract template to create an instance of.
amount (int): The amount in satoshis to fund the new contract instance with. This amount will be sent to the
contract's address in a funding transaction.
- data (Optional[ContractState], optional): For augmented P2TR contracts, this parameter should provide the initial
- state data to be embedded within the contract instance. For standard P2TR contracts, this must be None.
+ data (ContractState): The initial state data for the contract instance. Defaults to EMPTY_STATE for
+ stateless contracts.
Returns:
ContractInstance: The newly created and funded contract instance, which is now being managed by this ContractManager.
- Raises:
- ValueError: If an attempt is made to provide data for a (non-augmented) P2TR contract, or if data is not provided for
- an augmented P2TR contract.
-
Note:
- If `mine_automatically` is set to True, this method will also trigger the mining of a new block.
- The method blocks until the spend transaction is confirmed.
"""
instance = ContractInstance(contract)
-
- if isinstance(contract, StandardP2TR) and data is not None:
- raise ValueError("The data must be None for a contract with no embedded data")
-
- if isinstance(contract, StandardAugmentedP2TR):
- if data is None:
- raise ValueError("The data must be provided for an augmented P2TR contract instance")
- instance.data_expanded = data
- instance.data = data.encode()
+ instance.data_expanded = data
+ instance.data = data.encode()
self.add_instance(instance)
txid = self.rpc.sendtoaddress(instance.get_address(), amount/100_000_000)
self.wait_for_outpoint(instance, txid)
diff --git a/src/matt/script_helpers.py b/src/matt/script_helpers.py
index 1586b66..b1e767e 100644
--- a/src/matt/script_helpers.py
+++ b/src/matt/script_helpers.py
@@ -1,9 +1,9 @@
-from typing import Optional, Union
+from typing import Optional
from matt import CCV_FLAG_CHECK_INPUT
from matt.btctools.script import OP_2DROP, OP_2DUP, OP_2OVER, OP_3DUP, OP_CAT, OP_CHECKCONTRACTVERIFY, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_DUP, OP_FROMALTSTACK, OP_PICK, OP_SHA256, OP_TOALTSTACK, CScript
-from matt.contracts import StandardAugmentedP2TR, StandardP2TR
+from matt.contracts import StandardAugmentedP2TR
# Duplicates the last n elements of the stack
@@ -75,7 +75,7 @@ def check_input_contract(index: int = -1, pubkey: Optional[bytes] = None) -> CSc
# data --
-def check_output_contract(out_contract: Union[StandardP2TR, StandardAugmentedP2TR], index: int = -1, pubkey: Optional[bytes] = None) -> CScript:
+def check_output_contract(out_contract: StandardAugmentedP2TR, index: int = -1, pubkey: Optional[bytes] = None) -> CScript:
assert index >= -1
assert pubkey is None or len(pubkey) == 32
return CScript([
diff --git a/tests/conftest.py b/tests/conftest.py
index f836bd4..5d8f02c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -46,7 +46,7 @@ def manager(rpc, request: pytest.FixtureRequest, utxo_graph: bool):
create_utxo_graph(manager, f"tests/graphs/{request.node.name}.html")
-class TestReport:
+class Report:
def __init__(self):
self.sections = {}
@@ -66,6 +66,6 @@ def finalize_report(self, filename):
@pytest.fixture(scope="session")
def report():
- report_obj = TestReport()
+ report_obj = Report()
yield report_obj
report_obj.finalize_report("report.md")
diff --git a/tests/test_fraud.py b/tests/test_fraud.py
index 3f37c1c..166d23a 100644
--- a/tests/test_fraud.py
+++ b/tests/test_fraud.py
@@ -2,7 +2,7 @@
from matt.btctools.common import sha256
from matt.btctools.messages import CTxOut
-from matt.contracts import P2TR
+from matt.contracts import SimpleP2TR
from matt.hub.fraud import Bisect_1, Bisect_2, Leaf
from matt.manager import ContractManager, SchnorrSigner
from matt.merkle import is_power_of_2
@@ -34,7 +34,7 @@ def test_leaf_reveal_alice(manager: ContractManager):
outputs = [
CTxOut(
nValue=AMOUNT,
- scriptPubKey=P2TR(alice_key.pubkey[1:], []).get_tr_info().scriptPubKey
+ scriptPubKey=SimpleP2TR(alice_key.pubkey[1:]).get_tr_info(b'').scriptPubKey
)
]
@@ -63,7 +63,7 @@ def test_leaf_reveal_bob(manager: ContractManager):
outputs = [
CTxOut(
nValue=AMOUNT,
- scriptPubKey=P2TR(bob_key.pubkey[1:], []).get_tr_info().scriptPubKey
+ scriptPubKey=SimpleP2TR(bob_key.pubkey[1:]).get_tr_info(b'').scriptPubKey
)
]
@@ -264,7 +264,7 @@ def t_node_b(i, j) -> bytes:
outputs = [
CTxOut(
nValue=AMOUNT,
- scriptPubKey=P2TR(bob_key.pubkey[1:], []).get_tr_info().scriptPubKey
+ scriptPubKey=SimpleP2TR(bob_key.pubkey[1:]).get_tr_info(b'').scriptPubKey
)
]
out_instances = inst("bob_reveal", bob_signer, outputs)(