Skip to content

Chang support #371

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

Merged
merged 48 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7840715
Chang initial commit
cffls Jul 27, 2024
213899f
Fix docker compose
cffls Aug 4, 2024
3e3c591
Minor fixes
cffls Aug 11, 2024
157f23e
Add 60s of delay to startTime of genesis
cffls Aug 12, 2024
f6a89c5
Add an OgmiosV6 backend based on ogmios-python
nielstron Aug 21, 2024
0e285e5
Fix ogmios test
nielstron Aug 21, 2024
9449621
Fixes
nielstron Aug 27, 2024
ed52278
Re-add 3.8/3.9
nielstron Aug 27, 2024
b7d8b09
Bump ogmios requirement
nielstron Aug 27, 2024
3a3df8c
Fix mypy errors
nielstron Aug 27, 2024
85a5717
Merge branch 'feat/add_v6_backend' into chang
cffls Aug 29, 2024
f2abd83
Merge remote-tracking branch 'upstream/feat/add_v6_backend' into chang
cffls Aug 29, 2024
6e8f795
Fix python ogmios
cffls Aug 29, 2024
19e6177
Bump default major version to 9
cffls Aug 30, 2024
965dc64
Working plutus v3
cffls Aug 31, 2024
81c4602
Restore test retries
cffls Aug 31, 2024
6b757d8
Remove prints
cffls Aug 31, 2024
9bd906b
Remove more prints
cffls Aug 31, 2024
05a40f2
Format code
cffls Aug 31, 2024
0288645
Fix mypy
cffls Aug 31, 2024
1da49b6
Add tests
cffls Sep 1, 2024
92689b8
Add kupo back to integration test
cffls Sep 1, 2024
5b6bf37
Fix qa
cffls Sep 1, 2024
53264e7
Merge remote-tracking branch 'origin/main' into feat/add_v6_backend
nielstron Sep 2, 2024
5a3c080
Bump ogmios version
nielstron Sep 2, 2024
091673f
Add docker-compose to workflow
nielstron Sep 2, 2024
a8f5ce0
Fix docker-compose version
nielstron Sep 2, 2024
b829608
Use v6 chain context from pycardano itself
nielstron Sep 2, 2024
b0eb69c
Fix
nielstron Sep 2, 2024
19e099e
Merge remote-tracking branch 'upstream/feat/add_v6_backend' into chang
cffls Sep 2, 2024
94be3e3
Add PlutusV3 in a few places
nielstron Sep 2, 2024
37d71e3
Combine coverages
nielstron Sep 2, 2024
7243cd9
Support redeemer map
cffls Sep 3, 2024
fb57ed4
Automatically reduce 0 and empty multiassets (#372)
nielstron Sep 7, 2024
11dc71f
Using log.warning
cffls Sep 8, 2024
66218b1
Restore OgmiosChainBackend and fix docs
cffls Sep 8, 2024
080737d
Format
cffls Sep 8, 2024
05b150d
Blockfrost for chang
cffls Sep 8, 2024
527fb6b
Fix max tx fee
cffls Sep 17, 2024
295a987
Update upload-artifact to v4
cffls Sep 17, 2024
348c5a6
Refactor Redeemer handling in TransactionWitnessSet
bhatt-deep Sep 11, 2024
7727c72
Add tests for TransactionBuilder redeemer handling
bhatt-deep Sep 18, 2024
733f52f
Added unit test
xxAVOGADROxx Sep 21, 2024
388ab97
Run workflow for PRs to chang
nielstron Sep 21, 2024
fcda104
Allow users to configure the serialization format for redeemers
cffls Sep 21, 2024
043578d
Format
cffls Sep 21, 2024
46c453d
Set vkey witnesses to None when empty (#377)
theeldermillenial Sep 22, 2024
ba73b10
Fixed transaction imbalance when burning assets from the same policy …
xxAVOGADROxx Sep 22, 2024
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
Prev Previous commit
Next Next commit
Support redeemer map
  • Loading branch information
cffls committed Sep 3, 2024
commit 7243cd9e6e91caa8175898fcc870d3669e3af8ca
2 changes: 1 addition & 1 deletion pycardano/backend/blockfrost.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
ExecutionUnits,
PlutusV1Script,
PlutusV2Script,
script_hash,
PlutusV3Script,
script_hash,
)
from pycardano.serialization import RawCBOR
from pycardano.transaction import (
Expand Down Expand Up @@ -189,10 +189,10 @@
)
return _try_fix_script(script_hash, v2script)
elif script_type == "plutusV3":
v3script = PlutusV3Script(

Check warning on line 192 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L192

Added line #L192 was not covered by tests
bytes.fromhex(self.api.script_cbor(script_hash).cbor)
)
return _try_fix_script(script_hash, v3script)

Check warning on line 195 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L195

Added line #L195 was not covered by tests
else:
script_json: JsonDict = self.api.script_json(
script_hash, return_type="json"
Expand Down
58 changes: 57 additions & 1 deletion pycardano/plutus.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from dataclasses import dataclass, field, fields
from enum import Enum
from hashlib import sha256
from typing import Any, Optional, Type, Union
from typing import Any, List, Optional, Type, Union

import cbor2
from cbor2 import CBORTag
from nacl.encoding import RawEncoder
from nacl.hash import blake2b
from typeguard import typechecked

from pycardano.exception import DeserializeException, InvalidArgumentException
from pycardano.hash import DATUM_HASH_SIZE, SCRIPT_HASH_SIZE, DatumHash, ScriptHash
Expand Down Expand Up @@ -44,6 +45,10 @@
"PlutusV3Script",
"RawPlutusData",
"Redeemer",
"RedeemerKey",
"RedeemerValue",
"RedeemerMap",
"Redeemers",
"ScriptType",
"datum_hash",
"plutus_script_hash",
Expand Down Expand Up @@ -993,6 +998,57 @@
return redeemer


@dataclass(repr=False)
class RedeemerKey(ArrayCBORSerializable):
tag: RedeemerTag

index: int = field(default=0)

@classmethod
@limit_primitive_type(list, tuple)
def from_primitive(cls: Type[RedeemerKey], values: list) -> RedeemerKey:
tag = RedeemerTag.from_primitive(values[0])
index = values[1]
return cls(tag, index)

def __eq__(self, other):
if not isinstance(other, RedeemerKey):
return False

Check warning on line 1016 in pycardano/plutus.py

View check run for this annotation

Codecov / codecov/patch

pycardano/plutus.py#L1016

Added line #L1016 was not covered by tests
return self.tag == other.tag and self.index == other.index

def __hash__(self):
return hash(self.to_cbor())


@dataclass(repr=False)
class RedeemerValue(ArrayCBORSerializable):
data: Any

ex_units: ExecutionUnits

@classmethod
@limit_primitive_type(list)
def from_primitive(cls: Type[RedeemerValue], values: list) -> RedeemerValue:
if isinstance(values[0], CBORTag) and cls is RedeemerValue:
values[0] = RawPlutusData.from_primitive(values[0])

Check warning on line 1033 in pycardano/plutus.py

View check run for this annotation

Codecov / codecov/patch

pycardano/plutus.py#L1033

Added line #L1033 was not covered by tests
return super(RedeemerValue, cls).from_primitive([values[0], values[1]])

def __eq__(self, other):
if not isinstance(other, RedeemerValue):
return False

Check warning on line 1038 in pycardano/plutus.py

View check run for this annotation

Codecov / codecov/patch

pycardano/plutus.py#L1038

Added line #L1038 was not covered by tests
return self.data == other.data and self.ex_units == other.ex_units


@typechecked
class RedeemerMap(DictCBORSerializable):
KEY_TYPE = RedeemerKey

VALUE_TYPE = RedeemerValue


Redeemers = Union[List[Redeemer], RedeemerMap]


def plutus_script_hash(
script: Union[bytes, PlutusV1Script, PlutusV2Script]
) -> ScriptHash:
Expand Down
50 changes: 40 additions & 10 deletions pycardano/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dataclasses import dataclass, field, fields
from typing import Dict, List, Optional, Set, Tuple, Union

from pycardano import RedeemerMap
from pycardano.address import Address, AddressType
from pycardano.backend.base import ChainContext
from pycardano.certificate import (
Expand Down Expand Up @@ -42,7 +43,10 @@
PlutusV2Script,
PlutusV3Script,
Redeemer,
RedeemerKey,
Redeemers,
RedeemerTag,
RedeemerValue,
ScriptType,
datum_hash,
script_hash,
Expand Down Expand Up @@ -186,15 +190,15 @@
raise InvalidArgumentException(
f"All redeemers need to provide execution units if the firstly "
f"added redeemer specifies execution units. \n"
f"Added redeemers: {self.redeemers} \n"
f"Added redeemers: {self._redeemer_list} \n"
f"New redeemer: {redeemer}"
)
if self._should_estimate_execution_units:
if redeemer.ex_units:
raise InvalidArgumentException(
f"No redeemer should provide execution units if the firstly "
f"added redeemer didn't provide execution units. \n"
f"Added redeemers: {self.redeemers} \n"
f"Added redeemers: {self._redeemer_list} \n"
f"New redeemer: {redeemer}"
)
else:
Expand Down Expand Up @@ -485,16 +489,32 @@
return self._datums

@property
def redeemers(self) -> List[Redeemer]:
def _redeemer_list(self) -> List[Redeemer]:
return (
[r for r in self._inputs_to_redeemers.values() if r is not None]
+ [r for _, r in self._minting_script_to_redeemers if r is not None]
+ [r for _, r in self._withdrawal_script_to_redeemers if r is not None]
)

def redeemers(self) -> RedeemerMap:
redeemers = RedeemerMap()
for r in self._redeemer_list:
if r.tag is None:
raise InvalidArgumentException(

Check warning on line 503 in pycardano/txbuilder.py

View check run for this annotation

Codecov / codecov/patch

pycardano/txbuilder.py#L503

Added line #L503 was not covered by tests
f"Redeemer tag is not set. Redeemer: {r}"
)
if r.ex_units is None:
raise InvalidArgumentException(

Check warning on line 507 in pycardano/txbuilder.py

View check run for this annotation

Codecov / codecov/patch

pycardano/txbuilder.py#L507

Added line #L507 was not covered by tests
f"Execution units are not set. Redeemer: {r}"
)
k = RedeemerKey(r.tag, r.index)
v = RedeemerValue(r.data, r.ex_units)
redeemers[k] = v
return redeemers

@property
def script_data_hash(self) -> Optional[ScriptDataHash]:
if self.datums or self.redeemers:
if self.datums or self._redeemer_list:
cost_models = {}
for s in self.all_scripts:
if isinstance(s, PlutusV1Script) or type(s) is bytes:
Expand All @@ -512,7 +532,7 @@
"PlutusV3", {}
)
return script_data_hash(
self.redeemers, list(self.datums.values()), CostModels(cost_models)
self.redeemers(), list(self.datums.values()), CostModels(cost_models)
)
else:
return None
Expand Down Expand Up @@ -878,7 +898,7 @@
script_staking_credential.to_primitive()
)

self.redeemers.sort(key=lambda r: r.index)
self._redeemer_list.sort(key=lambda r: r.index)

def _build_tx_body(self) -> TransactionBody:
tx_body = TransactionBody(
Expand Down Expand Up @@ -946,13 +966,16 @@
)
return tx

def build_witness_set(self, remove_dup_script=False) -> TransactionWitnessSet:
def build_witness_set(
self, remove_dup_script: bool = False, post_chang: bool = True
) -> TransactionWitnessSet:
"""Build a transaction witness set, excluding verification key witnesses.
This function is especially useful when the transaction involves Plutus scripts.

Args:
remove_dup_script (bool): Whether to remove scripts, that are already attached to inputs,
from the witness set.
post_chang (bool): Whether to use chang serialization for the witness.

Returns:
TransactionWitnessSet: A transaction witness set without verification key witnesses.
Expand Down Expand Up @@ -986,16 +1009,23 @@
elif isinstance(script, PlutusV3Script):
plutus_v3_scripts.append(script)
else:
raise InvalidArgumentException(

Check warning on line 1012 in pycardano/txbuilder.py

View check run for this annotation

Codecov / codecov/patch

pycardano/txbuilder.py#L1012

Added line #L1012 was not covered by tests
f"Unsupported script type: {type(script)}"
)

redeemers: Optional[Redeemers] = None
if self._redeemer_list:
if not post_chang:
redeemers = self._redeemer_list
else:
redeemers = self.redeemers()

return 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,
redeemer=redeemers,
plutus_data=list(self.datums.values()) if self.datums else None,
)

Expand All @@ -1009,13 +1039,13 @@

def _estimate_fee(self):
plutus_execution_units = ExecutionUnits(0, 0)
for redeemer in self.redeemers:
for redeemer in self._redeemer_list:
plutus_execution_units += redeemer.ex_units

ref_script_size = 0
for s in self._reference_scripts:
if isinstance(s, NativeScript):
ref_script_size += len(s.to_cbor())

Check warning on line 1048 in pycardano/txbuilder.py

View check run for this annotation

Codecov / codecov/patch

pycardano/txbuilder.py#L1048

Added line #L1048 was not covered by tests
else:
ref_script_size += len(s)

Expand Down Expand Up @@ -1357,7 +1387,7 @@
estimated_execution_units = self._estimate_execution_units(
change_address, merge_change, collateral_change_address
)
for r in self.redeemers:
for r in self._redeemer_list:
assert (
r.tag is not None
), "Expected tag of redeemer to be set, but found None"
Expand Down
6 changes: 3 additions & 3 deletions pycardano/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from pycardano.backend.base import ChainContext
from pycardano.hash import SCRIPT_DATA_HASH_SIZE, SCRIPT_HASH_SIZE, ScriptDataHash
from pycardano.plutus import COST_MODELS, CostModels, Datum, Redeemer
from pycardano.plutus import COST_MODELS, CostModels, Datum, Redeemers
from pycardano.serialization import default_encoder
from pycardano.transaction import MultiAsset, TransactionOutput, Value

Expand Down Expand Up @@ -231,14 +231,14 @@ def min_lovelace_post_alonzo(output: TransactionOutput, context: ChainContext) -


def script_data_hash(
redeemers: List[Redeemer],
redeemers: Redeemers,
datums: List[Datum],
cost_models: Optional[Union[CostModels, Dict]] = None,
) -> ScriptDataHash:
"""Calculate plutus script data hash

Args:
redeemers (List[Redeemer]): Redeemers to include.
redeemers (Redeemers): Redeemers to include.
datums (List[Datum]): Datums to include.
cost_models (Optional[CostModels]): Cost models.

Expand Down
5 changes: 3 additions & 2 deletions pycardano/witness.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
PlutusV3Script,
RawPlutusData,
Redeemer,
Redeemers,
)
from pycardano.serialization import (
ArrayCBORSerializable,
Expand Down Expand Up @@ -76,9 +77,9 @@ class TransactionWitnessSet(MapCBORSerializable):
metadata={"optional": True, "key": 4, "object_hook": list_hook(RawPlutusData)},
)

redeemer: Optional[List[Redeemer]] = field(
redeemer: Optional[Redeemers] = field(
default=None,
metadata={"optional": True, "key": 5, "object_hook": list_hook(Redeemer)},
metadata={"optional": True, "key": 5},
)

plutus_v2_script: Optional[List[PlutusV2Script]] = field(
Expand Down
64 changes: 64 additions & 0 deletions test/pycardano/test_plutus.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
PlutusData,
RawPlutusData,
Redeemer,
RedeemerKey,
RedeemerMap,
RedeemerTag,
RedeemerValue,
Unit,
id_map,
plutus_script_hash,
Expand Down Expand Up @@ -520,3 +523,64 @@ class B(PlutusData):
)

assert B.from_cbor(cbor).to_cbor_hex() == cbor


def test_redeemer_key():
# Test creation and equality
key1 = RedeemerKey(RedeemerTag.SPEND, 0)
key2 = RedeemerKey(RedeemerTag.SPEND, 0)
key3 = RedeemerKey(RedeemerTag.MINT, 1)

assert key1 == key2
assert key1 != key3

# Test hashing
assert hash(key1) == hash(key2)
assert hash(key1) != hash(key3)

# Test serialization and deserialization
serialized = key1.to_primitive()
deserialized = RedeemerKey.from_primitive(serialized)
assert key1 == deserialized


def test_redeemer_value():
# Test creation
data = RawPlutusData(42)
ex_units = ExecutionUnits(10, 20)
value = RedeemerValue(data, ex_units)

assert value.data == data
assert value.ex_units == ex_units

# Test serialization and deserialization
serialized = value.to_primitive()
deserialized = RedeemerValue.from_primitive(serialized)
assert value.data.data == deserialized.data
assert value.ex_units == deserialized.ex_units


def test_redeemer_map():
# Test creation and adding items
redeemer_map = RedeemerMap()
key1 = RedeemerKey(RedeemerTag.SPEND, 0)
value1 = RedeemerValue(42, ExecutionUnits(10, 20))
key2 = RedeemerKey(RedeemerTag.MINT, 1)
value2 = RedeemerValue(b"test", ExecutionUnits(30, 40))

redeemer_map[key1] = value1
redeemer_map[key2] = value2

assert len(redeemer_map) == 2
assert redeemer_map[key1] == value1
assert redeemer_map[key2] == value2

# Test serialization and deserialization
serialized = redeemer_map.to_cbor()
deserialized = RedeemerMap.from_cbor(serialized)

assert len(deserialized) == 2
assert deserialized[key1].data == value1.data
assert deserialized[key1].ex_units == value1.ex_units
assert deserialized[key2].data == value2.data
assert deserialized[key2].ex_units == value2.ex_units
Loading
Loading