|
| 1 | +"""Tests for minting with Plutus V2 using `transaction build`.""" |
| 2 | +import logging |
| 3 | +from pathlib import Path |
| 4 | +from typing import List |
| 5 | +from typing import Optional |
| 6 | +from typing import Tuple |
| 7 | + |
| 8 | +import allure |
| 9 | +import pytest |
| 10 | +from _pytest.fixtures import FixtureRequest |
| 11 | +from cardano_clusterlib import clusterlib |
| 12 | + |
| 13 | +from cardano_node_tests.tests import common |
| 14 | +from cardano_node_tests.tests import plutus_common |
| 15 | +from cardano_node_tests.utils import cluster_management |
| 16 | +from cardano_node_tests.utils import clusterlib_utils |
| 17 | +from cardano_node_tests.utils import helpers |
| 18 | +from cardano_node_tests.utils.versions import VERSIONS |
| 19 | + |
| 20 | +LOGGER = logging.getLogger(__name__) |
| 21 | + |
| 22 | +pytestmark = [ |
| 23 | + pytest.mark.skipif( |
| 24 | + VERSIONS.transaction_era < VERSIONS.BABBAGE, |
| 25 | + reason="runs only with Babbage+ TX", |
| 26 | + ), |
| 27 | + pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), |
| 28 | + pytest.mark.smoke, |
| 29 | +] |
| 30 | + |
| 31 | + |
| 32 | +@pytest.fixture |
| 33 | +def payment_addrs( |
| 34 | + cluster_manager: cluster_management.ClusterManager, |
| 35 | + cluster: clusterlib.ClusterLib, |
| 36 | +) -> List[clusterlib.AddressRecord]: |
| 37 | + """Create new payment address.""" |
| 38 | + test_id = common.get_test_id(cluster) |
| 39 | + addrs = clusterlib_utils.create_payment_addr_records( |
| 40 | + *[f"{test_id}_payment_addr_{i}" for i in range(2)], |
| 41 | + cluster_obj=cluster, |
| 42 | + ) |
| 43 | + |
| 44 | + # fund source address |
| 45 | + clusterlib_utils.fund_from_faucet( |
| 46 | + addrs[0], |
| 47 | + cluster_obj=cluster, |
| 48 | + faucet_data=cluster_manager.cache.addrs_data["user1"], |
| 49 | + amount=3_000_000_000, |
| 50 | + ) |
| 51 | + |
| 52 | + return addrs |
| 53 | + |
| 54 | + |
| 55 | +def _fund_issuer( |
| 56 | + cluster_obj: clusterlib.ClusterLib, |
| 57 | + temp_template: str, |
| 58 | + payment_addr: clusterlib.AddressRecord, |
| 59 | + issuer_addr: clusterlib.AddressRecord, |
| 60 | + minting_cost: plutus_common.ScriptCost, |
| 61 | + amount: int, |
| 62 | + reference_script: Optional[Path] = None, |
| 63 | +) -> Tuple[ |
| 64 | + List[clusterlib.UTXOData], |
| 65 | + List[clusterlib.UTXOData], |
| 66 | + Optional[clusterlib.UTXOData], |
| 67 | + clusterlib.TxRawOutput, |
| 68 | +]: |
| 69 | + """Fund the token issuer.""" |
| 70 | + issuer_init_balance = cluster_obj.get_address_balance(issuer_addr.address) |
| 71 | + |
| 72 | + tx_files = clusterlib.TxFiles( |
| 73 | + signing_key_files=[payment_addr.skey_file], |
| 74 | + ) |
| 75 | + txouts = [ |
| 76 | + clusterlib.TxOut( |
| 77 | + address=issuer_addr.address, |
| 78 | + amount=amount, |
| 79 | + ), |
| 80 | + # for collateral |
| 81 | + clusterlib.TxOut(address=issuer_addr.address, amount=minting_cost.collateral), |
| 82 | + ] |
| 83 | + |
| 84 | + reference_amount = 0 |
| 85 | + if reference_script: |
| 86 | + reference_amount = 10_000_000 |
| 87 | + # for reference UTxO |
| 88 | + txouts.append( |
| 89 | + clusterlib.TxOut( |
| 90 | + address=issuer_addr.address, |
| 91 | + amount=reference_amount, |
| 92 | + reference_script_file=reference_script, |
| 93 | + ) |
| 94 | + ) |
| 95 | + |
| 96 | + tx_output = cluster_obj.build_tx( |
| 97 | + src_address=payment_addr.address, |
| 98 | + tx_name=f"{temp_template}_step1", |
| 99 | + tx_files=tx_files, |
| 100 | + txouts=txouts, |
| 101 | + fee_buffer=2_000_000, |
| 102 | + # don't join 'change' and 'collateral' txouts, we need separate UTxOs |
| 103 | + join_txouts=False, |
| 104 | + ) |
| 105 | + tx_signed = cluster_obj.sign_tx( |
| 106 | + tx_body_file=tx_output.out_file, |
| 107 | + signing_key_files=tx_files.signing_key_files, |
| 108 | + tx_name=f"{temp_template}_step1", |
| 109 | + ) |
| 110 | + cluster_obj.submit_tx(tx_file=tx_signed, txins=tx_output.txins) |
| 111 | + |
| 112 | + issuer_balance = cluster_obj.get_address_balance(issuer_addr.address) |
| 113 | + assert ( |
| 114 | + issuer_balance == issuer_init_balance + amount + minting_cost.collateral + reference_amount |
| 115 | + ), f"Incorrect balance for token issuer address `{issuer_addr.address}`" |
| 116 | + |
| 117 | + txid = cluster_obj.get_txid(tx_body_file=tx_output.out_file) |
| 118 | + mint_utxos = cluster_obj.get_utxo(txin=f"{txid}#1") |
| 119 | + collateral_utxos = cluster_obj.get_utxo(txin=f"{txid}#2") |
| 120 | + |
| 121 | + reference_utxo = None |
| 122 | + if reference_script: |
| 123 | + reference_utxos = cluster_obj.get_utxo(txin=f"{txid}#3") |
| 124 | + assert reference_utxos, "No reference script UTxO" |
| 125 | + reference_utxo = reference_utxos[0] |
| 126 | + |
| 127 | + return mint_utxos, collateral_utxos, reference_utxo, tx_output |
| 128 | + |
| 129 | + |
| 130 | +@pytest.mark.testnets |
| 131 | +class TestBuildMinting: |
| 132 | + """Tests for minting using Plutus smart contracts and `transaction build`.""" |
| 133 | + |
| 134 | + @allure.link(helpers.get_vcs_link()) |
| 135 | + @pytest.mark.parametrize( |
| 136 | + "use_reference_script", (True, False), ids=("reference_script", "script_file") |
| 137 | + ) |
| 138 | + def test_minting_one_token( |
| 139 | + self, |
| 140 | + cluster: clusterlib.ClusterLib, |
| 141 | + payment_addrs: List[clusterlib.AddressRecord], |
| 142 | + use_reference_script: bool, |
| 143 | + request: FixtureRequest, |
| 144 | + ): |
| 145 | + """Test minting a token with a Plutus script. |
| 146 | +
|
| 147 | + Uses `cardano-cli transaction build` command for building the transactions. |
| 148 | +
|
| 149 | + * fund the token issuer and create a UTxO for collateral and possibly reference script |
| 150 | + * check that the expected amount was transferred to token issuer's address |
| 151 | + * mint the token using a Plutus script |
| 152 | + * check that the token was minted and collateral UTxO was not spent |
| 153 | + * check expected fees |
| 154 | + * check expected Plutus cost |
| 155 | + """ |
| 156 | + # pylint: disable=too-many-locals |
| 157 | + temp_template = f"{common.get_test_id(cluster)}_{request.node.callspec.id}" |
| 158 | + payment_addr = payment_addrs[0] |
| 159 | + issuer_addr = payment_addrs[1] |
| 160 | + |
| 161 | + lovelace_amount = 2_000_000 |
| 162 | + token_amount = 5 |
| 163 | + script_fund = 200_000_000 |
| 164 | + |
| 165 | + minting_cost = plutus_common.compute_cost( |
| 166 | + execution_cost=plutus_common.MINTING_V2_COST, |
| 167 | + protocol_params=cluster.get_protocol_params(), |
| 168 | + ) |
| 169 | + |
| 170 | + issuer_init_balance = cluster.get_address_balance(issuer_addr.address) |
| 171 | + |
| 172 | + # Step 1: fund the token issuer and create UTxOs for collaterals and reference script |
| 173 | + |
| 174 | + mint_utxos, collateral_utxos, reference_utxo, tx_output_step1 = _fund_issuer( |
| 175 | + cluster_obj=cluster, |
| 176 | + temp_template=temp_template, |
| 177 | + payment_addr=payment_addr, |
| 178 | + issuer_addr=issuer_addr, |
| 179 | + minting_cost=minting_cost, |
| 180 | + amount=script_fund, |
| 181 | + reference_script=plutus_common.MINTING_PLUTUS_V2 if use_reference_script else None, |
| 182 | + ) |
| 183 | + assert reference_utxo or not use_reference_script, "No reference script UTxO" |
| 184 | + |
| 185 | + # Step 2: mint the "qacoin" |
| 186 | + |
| 187 | + policyid = cluster.get_policyid(plutus_common.MINTING_PLUTUS_V2) |
| 188 | + asset_name = f"qacoin{clusterlib.get_rand_str(4)}".encode("utf-8").hex() |
| 189 | + token = f"{policyid}.{asset_name}" |
| 190 | + mint_txouts = [ |
| 191 | + clusterlib.TxOut(address=issuer_addr.address, amount=token_amount, coin=token) |
| 192 | + ] |
| 193 | + |
| 194 | + plutus_mint_data = [ |
| 195 | + clusterlib.Mint( |
| 196 | + txouts=mint_txouts, |
| 197 | + script_file=plutus_common.MINTING_PLUTUS_V2 if not use_reference_script else "", |
| 198 | + reference_txin=reference_utxo if use_reference_script else None, |
| 199 | + collaterals=collateral_utxos, |
| 200 | + redeemer_file=plutus_common.REDEEMER_42, |
| 201 | + policyid=policyid, |
| 202 | + ) |
| 203 | + ] |
| 204 | + |
| 205 | + tx_files_step2 = clusterlib.TxFiles( |
| 206 | + signing_key_files=[issuer_addr.skey_file], |
| 207 | + ) |
| 208 | + txouts_step2 = [ |
| 209 | + clusterlib.TxOut(address=issuer_addr.address, amount=lovelace_amount), |
| 210 | + *mint_txouts, |
| 211 | + ] |
| 212 | + tx_output_step2 = cluster.build_tx( |
| 213 | + src_address=payment_addr.address, |
| 214 | + tx_name=f"{temp_template}_step2", |
| 215 | + tx_files=tx_files_step2, |
| 216 | + txins=mint_utxos, |
| 217 | + txouts=txouts_step2, |
| 218 | + mint=plutus_mint_data, |
| 219 | + ) |
| 220 | + plutus_cost = cluster.calculate_plutus_script_cost( |
| 221 | + src_address=payment_addr.address, |
| 222 | + tx_name=f"{temp_template}_step2", |
| 223 | + tx_files=tx_files_step2, |
| 224 | + txins=mint_utxos, |
| 225 | + txouts=txouts_step2, |
| 226 | + mint=plutus_mint_data, |
| 227 | + ) |
| 228 | + tx_signed_step2 = cluster.sign_tx( |
| 229 | + tx_body_file=tx_output_step2.out_file, |
| 230 | + signing_key_files=tx_files_step2.signing_key_files, |
| 231 | + tx_name=f"{temp_template}_step2", |
| 232 | + ) |
| 233 | + cluster.submit_tx(tx_file=tx_signed_step2, txins=mint_utxos) |
| 234 | + |
| 235 | + assert cluster.get_address_balance( |
| 236 | + issuer_addr.address |
| 237 | + ) == issuer_init_balance + minting_cost.collateral + lovelace_amount + ( |
| 238 | + reference_utxo.amount if reference_utxo else 0 |
| 239 | + ), f"Incorrect balance for token issuer address `{issuer_addr.address}`" |
| 240 | + |
| 241 | + token_utxo = cluster.get_utxo(address=issuer_addr.address, coins=[token]) |
| 242 | + assert token_utxo and token_utxo[0].amount == token_amount, "The token was NOT minted" |
| 243 | + |
| 244 | + # check that reference UTxO was NOT spent |
| 245 | + assert not reference_utxo or cluster.get_utxo( |
| 246 | + txin=f"{reference_utxo.utxo_hash}#{reference_utxo.utxo_ix}" |
| 247 | + ), "Reference UTxO was spent" |
| 248 | + |
| 249 | + # check expected fees |
| 250 | + if use_reference_script: |
| 251 | + expected_fee_step1 = 252_929 |
| 252 | + expected_fee_step2 = 230_646 |
| 253 | + expected_cost = plutus_common.MINTING_V2_REF_COST |
| 254 | + else: |
| 255 | + expected_fee_step1 = 167_437 |
| 256 | + expected_fee_step2 = 304_694 |
| 257 | + expected_cost = plutus_common.MINTING_V2_COST |
| 258 | + |
| 259 | + assert helpers.is_in_interval(tx_output_step2.fee, expected_fee_step2, frac=0.15) |
| 260 | + assert helpers.is_in_interval(tx_output_step1.fee, expected_fee_step1, frac=0.15) |
| 261 | + |
| 262 | + plutus_common.check_plutus_cost( |
| 263 | + plutus_cost=plutus_cost, |
| 264 | + expected_cost=[expected_cost], |
| 265 | + ) |
0 commit comments