From 4e7e28917137bec29804ee804980521b4bd171ba Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Fri, 20 Sep 2024 15:06:06 -0500 Subject: [PATCH 1/8] Fixed tx imbalance when burning multiple tokens --- pycardano/txbuilder.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index e03cc321..8297dd29 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -548,7 +548,26 @@ def _calc_change( for i in inputs: provided += i.output.amount if self.mint: - provided.multi_asset += self.mint + for policy_id, assets in self.mint.items(): + for asset_name, quantity in assets.items(): + provided.multi_asset[policy_id][asset_name] += quantity + + for policy_id in list(provided.multi_asset.keys()): + new_asset = Asset( + { + asset_name: quantity + for asset_name, quantity in provided.multi_asset[ + policy_id + ].items() + if quantity != 0 + } + ) + + if new_asset: + provided.multi_asset[policy_id] = new_asset + else: + del provided.multi_asset[policy_id] + if self.withdrawals: for v in self.withdrawals.values(): provided.coin += v From a34cc6e00cdbfd314f38b2671d29f8a33e0d95c9 Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Fri, 20 Sep 2024 19:10:33 -0500 Subject: [PATCH 2/8] Added cases and format --- pycardano/txbuilder.py | 7 +++++++ pycardano/witness.py | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 8297dd29..9d38b100 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -547,9 +547,16 @@ def _calc_change( provided = Value() for i in inputs: provided += i.output.amount + if self.mint: for policy_id, assets in self.mint.items(): for asset_name, quantity in assets.items(): + if policy_id not in provided.multi_asset: + provided.multi_asset[policy_id] = Asset() + + if asset_name not in provided.multi_asset[policy_id]: + provided.multi_asset[policy_id][asset_name] = 0 + provided.multi_asset[policy_id][asset_name] += quantity for policy_id in list(provided.multi_asset.keys()): diff --git a/pycardano/witness.py b/pycardano/witness.py index acceeb0f..e47ccf3b 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -8,17 +8,17 @@ from pycardano.key import ExtendedVerificationKey, VerificationKey from pycardano.nativescript import NativeScript from pycardano.plutus import ( + ExecutionUnits, PlutusV1Script, PlutusV2Script, PlutusV3Script, RawPlutusData, Redeemer, - Redeemers, - RedeemerMap, RedeemerKey, - RedeemerValue, - ExecutionUnits, + RedeemerMap, + Redeemers, RedeemerTag, + RedeemerValue, ) from pycardano.serialization import ( ArrayCBORSerializable, From 821a87be34b41040429c39c914be39a7285568ad Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Sat, 21 Sep 2024 01:45:46 -0500 Subject: [PATCH 3/8] Added unit test --- test/pycardano/test_txbuilder.py | 105 ++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 83f8aafc..cd05eff2 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -8,7 +8,7 @@ import pytest -from pycardano import RedeemerKey, RedeemerMap, RedeemerValue +from pycardano import AssetName, RedeemerKey, RedeemerMap, RedeemerValue from pycardano.address import Address from pycardano.certificate import ( PoolRegistration, @@ -1931,3 +1931,106 @@ def test_transaction_witness_set_no_redeemers(chain_context): tx_builder = TransactionBuilder(chain_context) witness_set = tx_builder.build_witness_set() assert witness_set.redeemer is None + + +def test_minting_and_burning_zero_quantity_assets(chain_context): + """ + Test the minting and burning of multiple assets using the TransactionBuilder. + + This test ensures that assets are correctly minted and burned under the same policy ID. + Specifically, it verifies that after burning certain assets (AssetName1, AssetName2, and AssetName3), + they are removed from the multi-asset map, and the correct amount of the minted asset (AssetName4) remains. + + Steps: + 1. Define a policy ID and several assets (AssetName1, AssetName2, AssetName3, and AssetName4) using the AssetName class. + 2. Simulate minting of 2 units of AssetName4 and burning 1 unit each of AssetName1, AssetName2, and AssetName3. + 3. Add corresponding UTXOs for each asset as inputs. + 4. Add minting instructions to the TransactionBuilder. + 5. Build the transaction and verify that the burnt assets are removed from the multi-asset map. + 6. Check that the correct quantity of AssetName4 is minted and included in the transaction outputs. + + Args: + chain_context: The blockchain context used for constructing and verifying the transaction. + + Assertions: + - AssetName1, AssetName2, and AssetName3 are not present in the multi-asset map after burning. + - AssetName4 has exactly 2 units minted. + """ + tx_builder = TransactionBuilder(chain_context) + + # Create change address + sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" + sender_address = Address.from_primitive(sender) + + # Create four transaction inputs + tx_in1 = TransactionInput.from_primitive( + ["a6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 0] + ) + tx_in2 = TransactionInput.from_primitive( + ["b6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 1] + ) + tx_in3 = TransactionInput.from_primitive( + ["c6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 2] + ) + tx_in4 = TransactionInput.from_primitive( + ["d6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 3] + ) + # Define a policy ID and asset names + policy_id = plutus_script_hash(PlutusV1Script(b"dummy script")) + multi_asset1 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName1": 1}}) + multi_asset2 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName2": 2}}) + multi_asset3 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName3": 1}}) + multi_asset4 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName4": 3}}) + + # Simulate minting and burning of assets + mint = MultiAsset.from_primitive( + { + policy_id.payload: { + b"AssetName1": -1, + b"AssetName2": -2, + b"AssetName3": -1, + b"AssetName4": 2, + } + } + ) + + # Set UTXO for the inputs + utxo1 = UTxO( + tx_in1, TransactionOutput(Address(policy_id), Value(10000000, multi_asset1)) + ) + utxo2 = UTxO( + tx_in2, TransactionOutput(Address(policy_id), Value(10000000, multi_asset2)) + ) + utxo3 = UTxO( + tx_in3, TransactionOutput(Address(policy_id), Value(10000000, multi_asset3)) + ) + utxo4 = UTxO( + tx_in4, TransactionOutput(Address(policy_id), Value(10000000, multi_asset4)) + ) + + # Add UTXO inputs + tx_builder.add_input(utxo1) + tx_builder.add_input(utxo2) + tx_builder.add_input(utxo3) + tx_builder.add_input(utxo4) + + # Add the minting to the builder + tx_builder.mint = mint + + # Build the transaction + tx = tx_builder.build(change_address=sender_address) + + # Check that the transaction has outputs + assert tx.outputs + + # Loop through the transaction outputs to verify the multi-asset quantities + for output in tx.outputs: + multi_asset = output.amount.multi_asset + + # Ensure that AssetName1, Node2, and Node3 were burnt (removed) + assert AssetName(b"AssetName1") not in multi_asset.get(policy_id, {}) + assert AssetName(b"AssetName2") not in multi_asset.get(policy_id, {}) + assert AssetName(b"AssetName3") not in multi_asset.get(policy_id, {}) + + # Ensure that AssetName4 has 5 units after minting + assert multi_asset.get(policy_id, {}).get(AssetName(b"AssetName4"), 0) == 5 From d7d50ded4219d92da69f009ac4f1c57870d9eae3 Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Sun, 22 Sep 2024 13:02:20 -0500 Subject: [PATCH 4/8] Correct unit test --- test/pycardano/test_txbuilder.py | 76 ++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index cd05eff2..c112ed05 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1933,28 +1933,29 @@ def test_transaction_witness_set_no_redeemers(chain_context): assert witness_set.redeemer is None -def test_minting_and_burning_zero_quantity_assets(chain_context): +def test_burning_assets_with_preserved_policy(chain_context): """ - Test the minting and burning of multiple assets using the TransactionBuilder. + Test the burning of assets under one policy ID while preserving assets under another policy ID. - This test ensures that assets are correctly minted and burned under the same policy ID. - Specifically, it verifies that after burning certain assets (AssetName1, AssetName2, and AssetName3), - they are removed from the multi-asset map, and the correct amount of the minted asset (AssetName4) remains. + This test ensures that assets are correctly burned under one policy (policy_id_1), while assets under another policy (policy_id_2) + remain unchanged. Specifically, it verifies that after burning certain assets (AssetName1, AssetName2, AssetName3, and AssetName4) + under policy_id_1, they are removed from the multi-asset map, while assets under policy_id_2 (AssetName5) remain intact. Steps: - 1. Define a policy ID and several assets (AssetName1, AssetName2, AssetName3, and AssetName4) using the AssetName class. - 2. Simulate minting of 2 units of AssetName4 and burning 1 unit each of AssetName1, AssetName2, and AssetName3. + 1. Define two policy IDs and several assets (AssetName1, AssetName2, AssetName3, AssetName4 under policy_id_1 + and AssetName5 under policy_id_2) using the AssetName class. + 2. Simulate burning of 1 unit each of AssetName1, AssetName2, AssetName3, and AssetName4 under policy_id_1. 3. Add corresponding UTXOs for each asset as inputs. - 4. Add minting instructions to the TransactionBuilder. + 4. Add burn instructions to the TransactionBuilder. 5. Build the transaction and verify that the burnt assets are removed from the multi-asset map. - 6. Check that the correct quantity of AssetName4 is minted and included in the transaction outputs. + 6. Check that the correct quantity of AssetName5 under policy_id_2 remains intact in the transaction outputs. Args: chain_context: The blockchain context used for constructing and verifying the transaction. Assertions: - - AssetName1, AssetName2, and AssetName3 are not present in the multi-asset map after burning. - - AssetName4 has exactly 2 units minted. + - AssetName1, AssetName2, AssetName3, and AssetName4 (under policy_id_1) are not present in the multi-asset map after burning. + - AssetName5 (under policy_id_2) remains with exactly 20 units in the transaction outputs. """ tx_builder = TransactionBuilder(chain_context) @@ -1976,43 +1977,51 @@ def test_minting_and_burning_zero_quantity_assets(chain_context): ["d6cbe6cadecd3f89b60e08e68e5e6c7d72d730aaa1ad21431590f7e6643438ef", 3] ) # Define a policy ID and asset names - policy_id = plutus_script_hash(PlutusV1Script(b"dummy script")) - multi_asset1 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName1": 1}}) - multi_asset2 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName2": 2}}) - multi_asset3 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName3": 1}}) - multi_asset4 = MultiAsset.from_primitive({policy_id.payload: {b"AssetName4": 3}}) + policy_id_1 = plutus_script_hash(PlutusV1Script(b"dummy script1")) + policy_id_2 = plutus_script_hash(PlutusV1Script(b"dummy script2")) + multi_asset1 = MultiAsset.from_primitive({policy_id_1.payload: {b"AssetName1": 1}}) + multi_asset2 = MultiAsset.from_primitive({policy_id_1.payload: {b"AssetName2": 1}}) + multi_asset3 = MultiAsset.from_primitive( + { + policy_id_1.payload: {b"AssetName3": 1}, + policy_id_2.payload: {b"AssetName5": 10}, + } + ) + multi_asset4 = MultiAsset.from_primitive( + { + policy_id_1.payload: {b"AssetName4": 1}, + policy_id_2.payload: {b"AssetName5": 10}, + } + ) # Simulate minting and burning of assets mint = MultiAsset.from_primitive( { - policy_id.payload: { + policy_id_1.payload: { b"AssetName1": -1, - b"AssetName2": -2, + b"AssetName2": -1, b"AssetName3": -1, - b"AssetName4": 2, + b"AssetName4": -1, } } ) # Set UTXO for the inputs utxo1 = UTxO( - tx_in1, TransactionOutput(Address(policy_id), Value(10000000, multi_asset1)) + tx_in1, TransactionOutput(Address(policy_id_1), Value(10000000, multi_asset1)) ) utxo2 = UTxO( - tx_in2, TransactionOutput(Address(policy_id), Value(10000000, multi_asset2)) + tx_in2, TransactionOutput(Address(policy_id_1), Value(10000000, multi_asset2)) ) utxo3 = UTxO( - tx_in3, TransactionOutput(Address(policy_id), Value(10000000, multi_asset3)) + tx_in3, TransactionOutput(Address(policy_id_1), Value(10000000, multi_asset3)) ) utxo4 = UTxO( - tx_in4, TransactionOutput(Address(policy_id), Value(10000000, multi_asset4)) + tx_in4, TransactionOutput(Address(policy_id_1), Value(10000000, multi_asset4)) ) # Add UTXO inputs - tx_builder.add_input(utxo1) - tx_builder.add_input(utxo2) - tx_builder.add_input(utxo3) - tx_builder.add_input(utxo4) + tx_builder.add_input(utxo1).add_input(utxo2).add_input(utxo3).add_input(utxo4) # Add the minting to the builder tx_builder.mint = mint @@ -2027,10 +2036,11 @@ def test_minting_and_burning_zero_quantity_assets(chain_context): for output in tx.outputs: multi_asset = output.amount.multi_asset - # Ensure that AssetName1, Node2, and Node3 were burnt (removed) - assert AssetName(b"AssetName1") not in multi_asset.get(policy_id, {}) - assert AssetName(b"AssetName2") not in multi_asset.get(policy_id, {}) - assert AssetName(b"AssetName3") not in multi_asset.get(policy_id, {}) + # Ensure that AssetName1, AssetName2, AssetName3 and AssetName4 were burnt (removed) + assert AssetName(b"AssetName1") not in multi_asset.get(policy_id_1, {}) + assert AssetName(b"AssetName2") not in multi_asset.get(policy_id_1, {}) + assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) + assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {}) - # Ensure that AssetName4 has 5 units after minting - assert multi_asset.get(policy_id, {}).get(AssetName(b"AssetName4"), 0) == 5 + # Ensure that 20 units of AssetName5 are retained from the input + assert multi_asset.get(policy_id_2, {}).get(AssetName(b"AssetName5"), 0) == 20 From c7a53c6df4d049f94d70056377866c304d30c336 Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Sun, 22 Sep 2024 13:14:36 -0500 Subject: [PATCH 5/8] Removed redundant filtering zero-quantity asset --- pycardano/txbuilder.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 9d38b100..c122514e 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -559,22 +559,6 @@ def _calc_change( provided.multi_asset[policy_id][asset_name] += quantity - for policy_id in list(provided.multi_asset.keys()): - new_asset = Asset( - { - asset_name: quantity - for asset_name, quantity in provided.multi_asset[ - policy_id - ].items() - if quantity != 0 - } - ) - - if new_asset: - provided.multi_asset[policy_id] = new_asset - else: - del provided.multi_asset[policy_id] - if self.withdrawals: for v in self.withdrawals.values(): provided.coin += v From 233b3dee300f41db83d93dd386572c8da8e1ecc9 Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Sun, 22 Sep 2024 13:21:17 -0500 Subject: [PATCH 6/8] Improved name function (unit test) --- test/pycardano/test_txbuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index c112ed05..8f20d8ce 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1933,7 +1933,7 @@ def test_transaction_witness_set_no_redeemers(chain_context): assert witness_set.redeemer is None -def test_burning_assets_with_preserved_policy(chain_context): +def test_minting_updates_multi_asset_correctly(chain_context): """ Test the burning of assets under one policy ID while preserving assets under another policy ID. From ad5015c9c000423367186b5c72153de8c359c857 Mon Sep 17 00:00:00 2001 From: xxAVOGADROxx Date: Sun, 22 Sep 2024 13:37:08 -0500 Subject: [PATCH 7/8] Improved unit test --- test/pycardano/test_txbuilder.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 8f20d8ce..f3bb874a 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -1933,29 +1933,23 @@ def test_transaction_witness_set_no_redeemers(chain_context): assert witness_set.redeemer is None -def test_minting_updates_multi_asset_correctly(chain_context): +def test_burning_all_assets_under_single_policy(chain_context): """ - Test the burning of assets under one policy ID while preserving assets under another policy ID. + Test burning all assets under a single policy with TransactionBuilder. - This test ensures that assets are correctly burned under one policy (policy_id_1), while assets under another policy (policy_id_2) - remain unchanged. Specifically, it verifies that after burning certain assets (AssetName1, AssetName2, AssetName3, and AssetName4) - under policy_id_1, they are removed from the multi-asset map, while assets under policy_id_2 (AssetName5) remain intact. + This test ensures that burning multiple assets (AssetName1, AssetName2, AssetName3, AssetName4) + under policy_id_1 removes them from the multi-asset map. Steps: - 1. Define two policy IDs and several assets (AssetName1, AssetName2, AssetName3, AssetName4 under policy_id_1 - and AssetName5 under policy_id_2) using the AssetName class. - 2. Simulate burning of 1 unit each of AssetName1, AssetName2, AssetName3, and AssetName4 under policy_id_1. - 3. Add corresponding UTXOs for each asset as inputs. - 4. Add burn instructions to the TransactionBuilder. - 5. Build the transaction and verify that the burnt assets are removed from the multi-asset map. - 6. Check that the correct quantity of AssetName5 under policy_id_2 remains intact in the transaction outputs. + 1. Define assets under policy_id_1 and simulate burning 1 unit of each. + 2. Add UTXOs for the assets and burning instructions. + 3. Build the transaction and verify that all burned assets are removed. Args: - chain_context: The blockchain context used for constructing and verifying the transaction. + chain_context: The blockchain context. Assertions: - - AssetName1, AssetName2, AssetName3, and AssetName4 (under policy_id_1) are not present in the multi-asset map after burning. - - AssetName5 (under policy_id_2) remains with exactly 20 units in the transaction outputs. + - AssetName1, AssetName2, AssetName3, and AssetName4 are removed after burning. """ tx_builder = TransactionBuilder(chain_context) @@ -1978,19 +1972,16 @@ def test_minting_updates_multi_asset_correctly(chain_context): ) # Define a policy ID and asset names policy_id_1 = plutus_script_hash(PlutusV1Script(b"dummy script1")) - policy_id_2 = plutus_script_hash(PlutusV1Script(b"dummy script2")) multi_asset1 = MultiAsset.from_primitive({policy_id_1.payload: {b"AssetName1": 1}}) multi_asset2 = MultiAsset.from_primitive({policy_id_1.payload: {b"AssetName2": 1}}) multi_asset3 = MultiAsset.from_primitive( { policy_id_1.payload: {b"AssetName3": 1}, - policy_id_2.payload: {b"AssetName5": 10}, } ) multi_asset4 = MultiAsset.from_primitive( { policy_id_1.payload: {b"AssetName4": 1}, - policy_id_2.payload: {b"AssetName5": 10}, } ) @@ -2041,6 +2032,3 @@ def test_minting_updates_multi_asset_correctly(chain_context): assert AssetName(b"AssetName2") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {}) - - # Ensure that 20 units of AssetName5 are retained from the input - assert multi_asset.get(policy_id_2, {}).get(AssetName(b"AssetName5"), 0) == 20 From 66ef7d5b7a16f2155e6bcfb7d1bd51e08a1841e0 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 22 Sep 2024 13:47:24 -0700 Subject: [PATCH 8/8] Fix __iadd__ in assets --- pycardano/transaction.py | 4 ++-- pycardano/txbuilder.py | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pycardano/transaction.py b/pycardano/transaction.py index 6c060101..5d5c7c0e 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -107,7 +107,7 @@ def __add__(self, other: Asset) -> Asset: def __iadd__(self, other: Asset) -> Asset: new_item = self + other - self.update(new_item) + self.data = new_item.data return self.normalize() def __sub__(self, other: Asset) -> Asset: @@ -173,7 +173,7 @@ def __add__(self, other): def __iadd__(self, other): new_item = self + other - self.update(new_item) + self.data = new_item.data return self.normalize() def __sub__(self, other: MultiAsset) -> MultiAsset: diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index c122514e..30e90597 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -549,15 +549,7 @@ def _calc_change( provided += i.output.amount if self.mint: - for policy_id, assets in self.mint.items(): - for asset_name, quantity in assets.items(): - if policy_id not in provided.multi_asset: - provided.multi_asset[policy_id] = Asset() - - if asset_name not in provided.multi_asset[policy_id]: - provided.multi_asset[policy_id][asset_name] = 0 - - provided.multi_asset[policy_id][asset_name] += quantity + provided.multi_asset += self.mint if self.withdrawals: for v in self.withdrawals.values():