Skip to content

Support PlutusV3Script #375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions pycardano/backend/blockfrost.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pycardano.hash import SCRIPT_HASH_SIZE, DatumHash, ScriptHash
from pycardano.nativescript import NativeScript
from pycardano.network import Network
from pycardano.plutus import ExecutionUnits, PlutusV1Script, PlutusV2Script, script_hash
from pycardano.plutus import ExecutionUnits, PlutusV1Script, PlutusV2Script, PlutusV3Script, script_hash
from pycardano.serialization import RawCBOR
from pycardano.transaction import (
Asset,
Expand All @@ -37,8 +37,8 @@


def _try_fix_script(
scripth: str, script: Union[PlutusV1Script, PlutusV2Script]
) -> Union[PlutusV1Script, PlutusV2Script]:
scripth: str, script: Union[PlutusV1Script, PlutusV2Script, PlutusV3Script]
) -> Union[PlutusV1Script, PlutusV2Script, PlutusV3Script]:
if str(script_hash(script)) == scripth:
return script
else:
Expand Down Expand Up @@ -170,7 +170,7 @@ def protocol_param(self) -> ProtocolParameters:

def _get_script(
self, script_hash: str
) -> Union[PlutusV1Script, PlutusV2Script, NativeScript]:
) -> Union[PlutusV1Script, PlutusV2Script, PlutusV3Script, NativeScript]:
script_type = self.api.script(script_hash).type
if script_type == "plutusV1":
v1script = PlutusV1Script(
Expand All @@ -182,6 +182,11 @@ def _get_script(
bytes.fromhex(self.api.script_cbor(script_hash).cbor)
)
return _try_fix_script(script_hash, v2script)
elif script_type == "plutusV3":
v3script = PlutusV3Script(
bytes.fromhex(self.api.script_cbor(script_hash).cbor)
)
return _try_fix_script(script_hash, v3script)
else:
script_json: JsonDict = self.api.script_json(
script_hash, return_type="json"
Expand Down
9 changes: 7 additions & 2 deletions pycardano/backend/cardano_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from pycardano.hash import DatumHash, ScriptHash
from pycardano.nativescript import NativeScript
from pycardano.network import Network
from pycardano.plutus import Datum, PlutusV1Script, PlutusV2Script, RawPlutusData
from pycardano.plutus import Datum, PlutusV1Script, PlutusV2Script, PlutusV3Script, RawPlutusData
from pycardano.serialization import RawCBOR
from pycardano.transaction import (
Asset,
Expand Down Expand Up @@ -384,7 +384,7 @@ def version(self):
@staticmethod
def _get_script(
reference_script: dict,
) -> Union[PlutusV1Script, PlutusV2Script, NativeScript]:
) -> Union[PlutusV1Script, PlutusV2Script, PlutusV3Script, NativeScript]:
"""
Get a script object from a reference script dictionary.
Args:
Expand All @@ -405,6 +405,11 @@ def _get_script(
cbor2.loads(bytes.fromhex(script_json["cborHex"]))
)
return v2script
elif script_type == "PlutusScriptV3":
v3script = PlutusV3Script(
cbor2.loads(bytes.fromhex(script_json["cborHex"]))
)
return v3script
else:
return NativeScript.from_dict(script_json)

Expand Down
19 changes: 12 additions & 7 deletions pycardano/backend/ogmios.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pycardano.exception import TransactionFailedException
from pycardano.hash import DatumHash, ScriptHash
from pycardano.network import Network
from pycardano.plutus import ExecutionUnits, PlutusV1Script, PlutusV2Script
from pycardano.plutus import ExecutionUnits, PlutusV1Script, PlutusV2Script, PlutusV3Script
from pycardano.serialization import RawCBOR
from pycardano.transaction import (
Asset,
Expand Down Expand Up @@ -342,11 +342,14 @@ def _utxos_kupo(self, address: str) -> List[UTxO]:
if script_hash:
kupo_script_url = self._kupo_url + "/scripts/" + script_hash
script = requests.get(kupo_script_url).json()
if script["language"] == "plutus:v2":
if script["language"] == "plutus:v1":
script = PlutusV1Script(bytes.fromhex(script["script"]))
script = _try_fix_script(script_hash, script)
elif script["language"] == "plutus:v2":
script = PlutusV2Script(bytes.fromhex(script["script"]))
script = _try_fix_script(script_hash, script)
elif script["language"] == "plutus:v1":
script = PlutusV1Script(bytes.fromhex(script["script"]))
elif script["language"] == "plutus:v3":
script = PlutusV3Script(bytes.fromhex(script["script"]))
script = _try_fix_script(script_hash, script)
else:
raise ValueError("Unknown plutus script type")
Expand Down Expand Up @@ -462,10 +465,12 @@ def _utxo_from_ogmios_result(self, result) -> UTxO:
lovelace_amount = output["value"]["coins"]
script = output.get("script", None)
if script:
if "plutus:v2" in script:
script = PlutusV2Script(bytes.fromhex(script["plutus:v2"]))
elif "plutus:v1" in script:
if "plutus:v1" in script:
script = PlutusV1Script(bytes.fromhex(script["plutus:v1"]))
elif "plutus:v2" in script:
script = PlutusV2Script(bytes.fromhex(script["plutus:v2"]))
elif "plutus:v3" in script:
script = PlutusV3Script(bytes.fromhex(script["plutus:v3"]))
else:
raise ValueError("Unknown plutus script type")
datum_hash = (
Expand Down
14 changes: 11 additions & 3 deletions pycardano/plutus.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ExecutionUnits",
"PlutusV1Script",
"PlutusV2Script",
"PlutusV3Script",
"RawPlutusData",
"Redeemer",
"ScriptType",
Expand Down Expand Up @@ -993,12 +994,12 @@ def from_primitive(cls: Type[Redeemer], values: list) -> Redeemer:


def plutus_script_hash(
script: Union[bytes, PlutusV1Script, PlutusV2Script]
script: Union[bytes, PlutusV1Script, PlutusV2Script, PlutusV3Script]
) -> ScriptHash:
"""Calculates the hash of a Plutus script.

Args:
script (Union[bytes, PlutusV1Script, PlutusV2Script]): A plutus script.
script (Union[bytes, PlutusV1Script, PlutusV2Script, PlutusV3Script]): A plutus script.

Returns:
ScriptHash: blake2b hash of the script.
Expand All @@ -1013,8 +1014,11 @@ class PlutusV1Script(bytes):
class PlutusV2Script(bytes):
pass

class PlutusV3Script(bytes):
pass


ScriptType = Union[bytes, NativeScript, PlutusV1Script, PlutusV2Script]
ScriptType = Union[bytes, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]
"""Script type. A Union type that contains all valid script types."""


Expand All @@ -1037,6 +1041,10 @@ def script_hash(script: ScriptType) -> ScriptHash:
return ScriptHash(
blake2b(bytes.fromhex("02") + script, SCRIPT_HASH_SIZE, encoder=RawEncoder)
)
elif isinstance(script, PlutusV3Script):
return ScriptHash(
blake2b(bytes.fromhex("03") + script, SCRIPT_HASH_SIZE, encoder=RawEncoder)
)
else:
raise TypeError(f"Unexpected script type: {type(script)}")

Expand Down
2 changes: 1 addition & 1 deletion pycardano/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ def _restore_typed_primitive(
if not isinstance(v, bytes):
raise DeserializeException(f"Expected type bytes but got {type(v)}")
return ByteString(v)
elif isclass(t) and t.__name__ in ["PlutusV1Script", "PlutusV2Script"]:
elif isclass(t) and t.__name__ in ["PlutusV1Script", "PlutusV2Script", "PlutusV3Script"]:
if not isinstance(v, bytes):
raise DeserializeException(f"Expected type bytes but got {type(v)}")
return t(v)
Expand Down
17 changes: 10 additions & 7 deletions pycardano/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from pycardano.metadata import AuxiliaryData
from pycardano.nativescript import NativeScript
from pycardano.network import Network
from pycardano.plutus import Datum, PlutusV1Script, PlutusV2Script, RawPlutusData
from pycardano.plutus import Datum, PlutusV1Script, PlutusV2Script, PlutusV3Script, RawPlutusData
from pycardano.serialization import (
ArrayCBORSerializable,
CBORSerializable,
Expand Down Expand Up @@ -259,15 +259,17 @@ def to_shallow_primitive(self):
class _Script(ArrayCBORSerializable):
_TYPE: int = field(init=False, default=0)

script: Union[NativeScript, PlutusV1Script, PlutusV2Script]
script: Union[NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]

def __post_init__(self):
if isinstance(self.script, NativeScript):
self._TYPE = 0
elif isinstance(self.script, PlutusV1Script):
self._TYPE = 1
else:
elif isinstance(self.script, PlutusV2Script):
self._TYPE = 2
else:
self._TYPE = 3

@classmethod
def from_primitive(cls: Type[_Script], values: List[Primitive]) -> _Script:
Expand All @@ -276,9 +278,10 @@ def from_primitive(cls: Type[_Script], values: List[Primitive]) -> _Script:
assert isinstance(values[1], bytes)
if values[0] == 1:
return cls(PlutusV1Script(values[1]))
else:
elif values[0] == 2:
return cls(PlutusV2Script(values[1]))

else:
return cls(PlutusV3Script(values[1]))

@dataclass(repr=False)
class _DatumOption(ArrayCBORSerializable):
Expand Down Expand Up @@ -344,7 +347,7 @@ class _TransactionOutputPostAlonzo(MapCBORSerializable):
)

@property
def script(self) -> Optional[Union[NativeScript, PlutusV1Script, PlutusV2Script]]:
def script(self) -> Optional[Union[NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]]:
if self.script_ref:
return self.script_ref.script.script
else:
Expand All @@ -370,7 +373,7 @@ class TransactionOutput(CBORSerializable):

datum: Optional[Datum] = None

script: Optional[Union[NativeScript, PlutusV1Script, PlutusV2Script]] = None
script: Optional[Union[NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]] = None

post_alonzo: Optional[bool] = False

Expand Down
26 changes: 18 additions & 8 deletions pycardano/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
ExecutionUnits,
PlutusV1Script,
PlutusV2Script,
PlutusV3Script,
Redeemer,
RedeemerTag,
ScriptType,
Expand Down Expand Up @@ -155,7 +156,7 @@ class TransactionBuilder:
init=False, default_factory=lambda: {}
)

_reference_scripts: List[Union[NativeScript, PlutusV1Script, PlutusV2Script]] = (
_reference_scripts: List[Union[NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]] = (
field(init=False, default_factory=lambda: [])
)

Expand Down Expand Up @@ -203,7 +204,7 @@ def add_script_input(
self,
utxo: UTxO,
script: Optional[
Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script]
Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]
] = None,
datum: Optional[Datum] = None,
redeemer: Optional[Redeemer] = None,
Expand All @@ -212,7 +213,7 @@ def add_script_input(

Args:
utxo (UTxO): Script UTxO to be added.
script (Optional[Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script]]): A plutus script.
script (Optional[Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script]]): A plutus script.
If not provided, the script will be inferred from the input UTxO (first arg of this method).
The script can also be a specific UTxO whose output contains an inline script.
datum (Optional[Datum]): A plutus datum to unlock the UTxO.
Expand Down Expand Up @@ -263,7 +264,7 @@ def add_script_input(

# collect potential scripts to fulfill the input
candidate_scripts: List[
Tuple[Union[NativeScript, PlutusV1Script, PlutusV2Script], Optional[UTxO]]
Tuple[Union[NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script], Optional[UTxO]]
] = []
if utxo.output.script:
candidate_scripts.append((utxo.output.script, utxo))
Expand Down Expand Up @@ -301,13 +302,13 @@ def add_script_input(

def add_minting_script(
self,
script: Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script],
script: Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script],
redeemer: Optional[Redeemer] = None,
) -> TransactionBuilder:
"""Add a minting script along with its datum and redeemer to this transaction.

Args:
script (Union[UTxO, PlutusV1Script, PlutusV2Script]): A plutus script.
script (Union[UTxO, PlutusV1Script, PlutusV2Script, PlutusV3Script]): A plutus script.
redeemer (Optional[Redeemer]): A plutus redeemer to unlock the UTxO.

Returns:
Expand All @@ -333,13 +334,13 @@ def add_minting_script(

def add_withdrawal_script(
self,
script: Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script],
script: Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script],
redeemer: Optional[Redeemer] = None,
) -> TransactionBuilder:
"""Add a withdrawal script along with its redeemer to this transaction.

Args:
script (Union[UTxO, PlutusV1Script, PlutusV2Script]): A plutus script.
script (Union[UTxO, PlutusV1Script, PlutusV2Script, PlutusV3Script]): A plutus script.
redeemer (Optional[Redeemer]): A plutus redeemer to unlock the UTxO.

Returns:
Expand Down Expand Up @@ -495,6 +496,11 @@ def script_data_hash(self) -> Optional[ScriptDataHash]:
self.context.protocol_param.cost_models.get("PlutusV2")
or PLUTUS_V2_COST_MODEL
)
if isinstance(s, PlutusV3Script):
cost_models[1] = (
self.context.protocol_param.cost_models.get("PlutusV3")
or PLUTUS_V2_COST_MODEL
)
return script_data_hash(
self.redeemers, list(self.datums.values()), CostModels(cost_models)
)
Expand Down Expand Up @@ -941,6 +947,7 @@ def build_witness_set(self) -> TransactionWitnessSet:
native_scripts: List[NativeScript] = []
plutus_v1_scripts: List[PlutusV1Script] = []
plutus_v2_scripts: List[PlutusV2Script] = []
plutus_v3_scripts: List[PlutusV3Script] = []

for script in self.scripts:
if isinstance(script, NativeScript):
Expand All @@ -951,6 +958,8 @@ def build_witness_set(self) -> TransactionWitnessSet:
plutus_v1_scripts.append(PlutusV1Script(script))
elif isinstance(script, PlutusV2Script):
plutus_v2_scripts.append(script)
elif isinstance(script, PlutusV3Script):
plutus_v3_scripts.append(script)
else:
raise InvalidArgumentException(
f"Unsupported script type: {type(script)}"
Expand All @@ -960,6 +969,7 @@ def build_witness_set(self) -> TransactionWitnessSet:
native_scripts=native_scripts if native_scripts else None,
plutus_v1_script=plutus_v1_scripts if plutus_v1_scripts else None,
plutus_v2_script=plutus_v2_scripts if plutus_v2_scripts else None,
plutus_v3_script=plutus_v3_scripts if plutus_v3_scripts else None,
redeemer=self.redeemers if self.redeemers else None,
plutus_data=list(self.datums.values()) if self.datums else None,
)
Expand Down
9 changes: 8 additions & 1 deletion pycardano/witness.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pycardano.key import ExtendedVerificationKey, VerificationKey
from pycardano.nativescript import NativeScript
from pycardano.plutus import PlutusV1Script, PlutusV2Script, RawPlutusData, Redeemer
from pycardano.plutus import PlutusV1Script, PlutusV2Script, PlutusV3Script, RawPlutusData, Redeemer
from pycardano.serialization import (
ArrayCBORSerializable,
MapCBORSerializable,
Expand Down Expand Up @@ -69,6 +69,10 @@ class TransactionWitnessSet(MapCBORSerializable):
default=None, metadata={"optional": True, "key": 6}
)

plutus_v3_script: Optional[List[PlutusV3Script]] = field(
default=None, metadata={"optional": True, "key": 7}
)

plutus_data: Optional[List[Any]] = field(
default=None,
metadata={"optional": True, "key": 4, "object_hook": list_hook(RawPlutusData)},
Expand Down Expand Up @@ -104,6 +108,9 @@ def _get_plutus_v1_scripts(data: Any):
def _get_plutus_v2_scripts(data: Any):
return [PlutusV2Script(script) for script in data] if data else None

def _get_plutus_v3_scripts(data: Any):
return [PlutusV3Script(script) for script in data] if data else None

def _get_redeemers(data: Any):
return (
[Redeemer.from_primitive(redeemer) for redeemer in data]
Expand Down
4 changes: 3 additions & 1 deletion test/pycardano/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pycardano
from pycardano import Datum, RawPlutusData
from pycardano.exception import DeserializeException
from pycardano.plutus import PlutusV1Script, PlutusV2Script
from pycardano.plutus import PlutusV1Script, PlutusV2Script, PlutusV3Script
from pycardano.serialization import (
ArrayCBORSerializable,
CBORSerializable,
Expand Down Expand Up @@ -295,10 +295,12 @@ def test_script_deserialize():
class Test(MapCBORSerializable):
script_1: PlutusV1Script
script_2: PlutusV2Script
script_3: PlutusV3Script

datum = Test(
script_1=PlutusV1Script(b"dummy test script"),
script_2=PlutusV2Script(b"dummy test script"),
script_3=PlutusV3Script(b"dummy test script"),
)

assert datum == datum.from_cbor(datum.to_cbor())
Expand Down
Loading