def check_plutus_cost(plutus_cost: List[dict],
                      expected_cost: List[ExecutionCost]):
    """Check plutus transaction cost.

    units: the time is in picoseconds and the space is in bytes.
    """
    # sort records by total cost
    sorted_plutus = sorted(
        plutus_cost,
        key=lambda x: x["executionUnits"]["memory"]  # type: ignore
        + x["executionUnits"]["steps"] + x["lovelaceCost"],
    )
    sorted_expected = sorted(
        expected_cost, key=lambda x: x.per_space + x.per_time + x.fixed_cost)

    errors = []
    for costs, expected_values in zip(sorted_plutus, sorted_expected):
        tx_time = costs["executionUnits"]["steps"]
        tx_space = costs["executionUnits"]["memory"]
        lovelace_cost = costs["lovelaceCost"]

        if not helpers.is_in_interval(
                tx_time, expected_values.per_time, frac=0.15):
            errors.append(f"time: {tx_time} vs {expected_values.per_time}")
        if not helpers.is_in_interval(
                tx_space, expected_values.per_space, frac=0.15):
            errors.append(f"space: {tx_space} vs {expected_values.per_space}")
        if not helpers.is_in_interval(
                lovelace_cost, expected_values.fixed_cost, frac=0.15):
            errors.append(
                f"fixed cost: {lovelace_cost} vs {expected_values.fixed_cost}")

    if errors:
        raise AssertionError("\n".join(errors))
    def test_addr_deregistration_fees(
        self,
        cluster: clusterlib.ClusterLib,
        pool_users: List[clusterlib.PoolUser],
        addr_fee: Tuple[int, int],
    ):
        """Test stake address deregistration fees."""
        no_of_addr, expected_fee = addr_fee
        temp_template = f"{helpers.get_func_name()}_{no_of_addr}"
        src_address = pool_users[0].payment.address
        selected_users = pool_users[:no_of_addr]

        stake_addr_dereg_certs = [
            cluster.gen_stake_addr_deregistration_cert(
                addr_name=f"{temp_template}_addr{i}",
                stake_vkey_file=p.stake.vkey_file)
            for i, p in enumerate(selected_users)
        ]

        # create TX data
        tx_files = clusterlib.TxFiles(
            certificate_files=[*stake_addr_dereg_certs],
            signing_key_files=[
                *[p.payment.skey_file for p in selected_users],
                *[p.stake.skey_file for p in selected_users],
            ],
        )

        # calculate TX fee
        tx_fee = cluster.calculate_tx_fee(src_address=src_address,
                                          tx_name=temp_template,
                                          tx_files=tx_files)
        assert helpers.is_in_interval(
            tx_fee, expected_fee), "Expected fee doesn't match the actual fee"
示例#3
0
    def _from_to_transactions(
        self,
        cluster_obj: clusterlib.ClusterLib,
        tx_name: str,
        pool_users: List[clusterlib.PoolUser],
        from_num: int,
        to_num: int,
        amount_expected: Tuple[int, int],
    ):
        """Check fees for 1 tx from `from_num` payment addresses to `to_num` payment addresses."""
        amount, expected_fee = amount_expected

        src_address = pool_users[0].payment.address
        # addr1..addr<from_num+1>
        from_addr_recs = [p.payment for p in pool_users[1 : from_num + 1]]
        # addr<from_num+1>..addr<from_num+to_num+1>
        dst_addresses = [
            pool_users[i].payment.address for i in range(from_num + 1, from_num + to_num + 1)
        ]

        # create TX data
        _txins = [cluster_obj.get_utxo(address=r.address) for r in from_addr_recs]
        # flatten the list of lists that is _txins
        txins = list(itertools.chain.from_iterable(_txins))
        txouts = [clusterlib.TxOut(address=addr, amount=amount) for addr in dst_addresses]
        tx_files = clusterlib.TxFiles(signing_key_files=[r.skey_file for r in from_addr_recs])

        # calculate TX fee
        tx_fee = cluster_obj.calculate_tx_fee(
            src_address=src_address, tx_name=tx_name, txins=txins, txouts=txouts, tx_files=tx_files
        )
        assert helpers.is_in_interval(
            tx_fee, expected_fee
        ), "Expected fee doesn't match the actual fee"
    def test_pool_registration_fees(
        self,
        cluster: clusterlib.ClusterLib,
        temp_dir: Path,
        pool_users: List[clusterlib.PoolUser],
        addr_fee: Tuple[int, int],
    ):
        """Test pool registration fees."""
        no_of_addr, expected_fee = addr_fee
        rand_str = clusterlib.get_rand_str(4)
        temp_template = f"{helpers.get_func_name()}_{rand_str}_{no_of_addr}"

        pool_name = f"pool_{rand_str}"
        pool_metadata = {
            "name": pool_name,
            "description": "Shelley QA E2E test Test",
            "ticker": "QA1",
            "homepage": "www.test1.com",
        }
        pool_metadata_file = helpers.write_json(
            temp_dir / f"{pool_name}_registration_metadata.json",
            pool_metadata)

        pool_data = clusterlib.PoolData(
            pool_name=pool_name,
            pool_pledge=1000,
            pool_cost=15,
            pool_margin=0.2,
            pool_metadata_url="https://www.where_metadata_file_is_located.com",
            pool_metadata_hash=cluster.gen_pool_metadata_hash(
                pool_metadata_file),
        )

        # create pool owners
        selected_owners = pool_users[:no_of_addr]

        # create certificates
        src_address, tx_files = self._create_pool_certificates(
            cluster_obj=cluster,
            pool_owners=selected_owners,
            temp_template=temp_template,
            pool_data=pool_data,
        )

        # calculate TX fee
        tx_fee = cluster.calculate_tx_fee(src_address=src_address,
                                          tx_name=temp_template,
                                          tx_files=tx_files)
        assert helpers.is_in_interval(
            tx_fee, expected_fee), "Expected fee doesn't match the actual fee"
示例#5
0
    def test_oversaturated(  # noqa: C901
        self,
        cluster_manager: cluster_management.ClusterManager,
        cluster_lock_pools: clusterlib.ClusterLib,
    ):
        """Check diminished rewards when stake pool is oversaturated.

        The stake pool continues to operate normally and those who delegate to that pool receive
        rewards, but the rewards are proportionally lower than those received from stake pool
        that is not oversaturated.

        * register and delegate stake address in "init epoch", for all available pools
        * in "init epoch" + 2, saturate all available pools (block distribution remains balanced
          among pools)
        * in "init epoch" + 3, oversaturate one pool
        * in "init epoch" + 5, for all available pools, withdraw rewards and transfer funds
          from delegated addresses so pools are no longer (over)saturated
        * while doing the steps above, collect rewards data for 9 epochs
        * compare proportionality of rewards in epochs where pools were non-saturated,
          saturated and oversaturated
        """
        # pylint: disable=too-many-statements,too-many-locals,too-many-branches
        epoch_saturate = 2
        epoch_oversaturate = 4
        epoch_withdrawal = 6

        cluster = cluster_lock_pools
        temp_template = common.get_test_id(cluster)
        initial_balance = 1_000_000_000

        faucet_rec = cluster_manager.cache.addrs_data["byron000"]
        pool_records: Dict[int, PoolRecord] = {}

        # make sure we have enough time to finish the delegation in one epoch
        clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster,
                                                 start=5,
                                                 stop=-40)
        init_epoch = cluster.get_epoch()

        # submit registration certificates and delegate to pools
        for idx, res in enumerate(
            [
                cluster_management.Resources.POOL1,
                cluster_management.Resources.POOL2,
                cluster_management.Resources.POOL3,
            ],
                start=1,
        ):
            pool_addrs_data = cluster_manager.cache.addrs_data[res]
            reward_addr = clusterlib.PoolUser(
                payment=pool_addrs_data["payment"],
                stake=pool_addrs_data["reward"])
            pool_id = delegation.get_pool_id(
                cluster_obj=cluster,
                addrs_data=cluster_manager.cache.addrs_data,
                pool_name=res,
            )
            pool_id_dec = helpers.decode_bech32(bech32=pool_id)

            delegation_out = delegation.delegate_stake_addr(
                cluster_obj=cluster,
                addrs_data=cluster_manager.cache.addrs_data,
                temp_template=f"{temp_template}_pool{idx}",
                pool_id=pool_id,
                amount=initial_balance,
            )

            pool_records[idx] = PoolRecord(
                name=res,
                id=pool_id,
                id_dec=pool_id_dec,
                reward_addr=reward_addr,
                delegation_out=delegation_out,
                user_rewards=[],
                owner_rewards=[],
                blocks_minted={},
                saturation_amounts={},
            )

        # record initial reward balance for each pool
        for pool_rec in pool_records.values():
            user_payment_balance = cluster.get_address_balance(
                pool_rec.delegation_out.pool_user.payment.address)
            owner_payment_balance = cluster.get_address_balance(
                pool_rec.reward_addr.payment.address)
            pool_rec.user_rewards.append(
                RewardRecord(
                    epoch_no=init_epoch,
                    reward_total=0,
                    reward_per_epoch=0,
                    stake_total=user_payment_balance,
                ))
            pool_rec.owner_rewards.append(
                RewardRecord(
                    epoch_no=init_epoch,
                    reward_total=cluster.get_stake_addr_info(
                        pool_rec.reward_addr.stake.address).
                    reward_account_balance,
                    reward_per_epoch=0,
                    stake_total=owner_payment_balance,
                ))

        assert (
            cluster.get_epoch() == init_epoch
        ), "Delegation took longer than expected and would affect other checks"

        LOGGER.info("Checking rewards for 10 epochs.")
        for __ in range(10):
            # wait for new epoch
            if cluster.get_epoch(
            ) == pool_records[2].owner_rewards[-1].epoch_no:
                cluster.wait_for_new_epoch()

            # sleep till the end of epoch
            clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster,
                                                     start=-50,
                                                     stop=-40,
                                                     force_epoch=True)
            this_epoch = cluster.get_epoch()

            ledger_state = clusterlib_utils.get_ledger_state(
                cluster_obj=cluster)
            clusterlib_utils.save_ledger_state(
                cluster_obj=cluster,
                state_name=f"{temp_template}_{this_epoch}",
                ledger_state=ledger_state,
            )

            for pool_rec in pool_records.values():
                # reward balance in previous epoch
                prev_user_reward = pool_rec.user_rewards[-1].reward_total
                prev_owner_reward = pool_rec.owner_rewards[-1].reward_total

                pool_rec.blocks_minted[this_epoch -
                                       1] = (ledger_state["blocksBefore"].get(
                                           pool_rec.id_dec) or 0)

                # current reward balance
                user_reward = cluster.get_stake_addr_info(
                    pool_rec.delegation_out.pool_user.stake.address
                ).reward_account_balance
                owner_reward = cluster.get_stake_addr_info(
                    pool_rec.reward_addr.stake.address).reward_account_balance

                # total reward amounts received this epoch
                owner_reward_epoch = owner_reward - prev_owner_reward
                # We cannot compare with previous rewards in epochs where
                # `this_epoch >= init_epoch + epoch_withdrawal`.
                # There's a withdrawal of rewards at the end of these epochs.
                if this_epoch > init_epoch + epoch_withdrawal:
                    user_reward_epoch = user_reward
                else:
                    user_reward_epoch = user_reward - prev_user_reward

                # store collected rewards info
                user_payment_balance = cluster.get_address_balance(
                    pool_rec.delegation_out.pool_user.payment.address)
                owner_payment_balance = cluster.get_address_balance(
                    pool_rec.reward_addr.payment.address)
                pool_rec.user_rewards.append(
                    RewardRecord(
                        epoch_no=this_epoch,
                        reward_total=user_reward,
                        reward_per_epoch=user_reward_epoch,
                        stake_total=user_payment_balance + user_reward,
                    ))
                pool_rec.owner_rewards.append(
                    RewardRecord(
                        epoch_no=this_epoch,
                        reward_total=owner_reward,
                        reward_per_epoch=owner_reward_epoch,
                        stake_total=owner_payment_balance,
                    ))

                pool_rec.saturation_amounts[
                    this_epoch] = _get_saturation_threshold(
                        cluster_obj=cluster,
                        ledger_state=ledger_state,
                        pool_id=pool_rec.id)

            # fund the delegated addresses - saturate all pools
            if this_epoch == init_epoch + epoch_saturate:
                clusterlib_utils.fund_from_faucet(
                    *[
                        p.delegation_out.pool_user.payment
                        for p in pool_records.values()
                    ],
                    cluster_obj=cluster,
                    faucet_data=faucet_rec,
                    amount=[
                        p.saturation_amounts[this_epoch] - 100_000_000_000
                        for p in pool_records.values()
                    ],
                    tx_name=f"{temp_template}_saturate_pools_ep{this_epoch}",
                    force=True,
                )

            with cluster_manager.restart_on_failure():
                # Fund the address delegated to "pool2" to oversaturate the pool.
                # New stake amount will be current (saturated) stake * 2.
                if this_epoch == init_epoch + epoch_oversaturate:
                    assert (pool_records[2].saturation_amounts[this_epoch] >
                            0), "Pool is already saturated"
                    current_stake = int(
                        cluster.get_stake_snapshot(
                            pool_records[2].id)["poolStakeMark"])
                    overstaturate_amount = current_stake * 2
                    saturation_threshold = pool_records[2].saturation_amounts[
                        this_epoch]
                    assert overstaturate_amount > saturation_threshold, (
                        f"{overstaturate_amount} Lovelace is not enough to oversature the pool "
                        f"({saturation_threshold} is needed)")
                    clusterlib_utils.fund_from_faucet(
                        pool_records[2].delegation_out.pool_user.payment,
                        cluster_obj=cluster,
                        faucet_data=faucet_rec,
                        amount=overstaturate_amount,
                        tx_name=f"{temp_template}_oversaturate_pool2",
                        force=True,
                    )

                # wait 4 epochs for first rewards
                if this_epoch >= init_epoch + 4:
                    assert (owner_reward > prev_owner_reward
                            ), "New reward was not received by pool owner"

                # transfer funds back to faucet so the pools are no longer (over)saturated
                # and staked amount is +- same as the `initial_balance`
                if this_epoch >= init_epoch + epoch_withdrawal:
                    _withdraw_rewards(
                        *[
                            p.delegation_out.pool_user
                            for p in pool_records.values()
                        ],
                        cluster_obj=cluster,
                        tx_name=f"{temp_template}_ep{this_epoch}",
                    )

                    return_to_addrs = []
                    return_amounts = []
                    for idx, pool_rec in pool_records.items():
                        deleg_payment_balance = cluster.get_address_balance(
                            pool_rec.delegation_out.pool_user.payment.address)
                        if deleg_payment_balance > initial_balance + 10_000_000:
                            return_to_addrs.append(
                                pool_rec.delegation_out.pool_user.payment)
                            return_amounts.append(deleg_payment_balance -
                                                  initial_balance)

                    clusterlib_utils.return_funds_to_faucet(
                        *return_to_addrs,
                        cluster_obj=cluster,
                        faucet_addr=faucet_rec["payment"].address,
                        amount=return_amounts,
                        tx_name=f"{temp_template}_ep{this_epoch}",
                    )

                    for return_addr in return_to_addrs:
                        deleg_payment_balance = cluster.get_address_balance(
                            return_addr.address)
                        assert (
                            deleg_payment_balance <= initial_balance
                        ), "Unexpected funds in payment address '{return_addr}'"

                assert (
                    cluster.get_epoch() == this_epoch
                ), "Failed to finish actions in single epoch, it would affect other checks"

        pool1_user_rewards_per_block = _get_reward_per_block(pool_records[1])
        pool2_user_rewards_per_block = _get_reward_per_block(pool_records[2])
        pool3_user_rewards_per_block = _get_reward_per_block(pool_records[3])

        pool1_owner_rewards_per_block = _get_reward_per_block(
            pool_records[1], owner_rewards=True)
        pool2_owner_rewards_per_block = _get_reward_per_block(
            pool_records[2], owner_rewards=True)
        pool3_owner_rewards_per_block = _get_reward_per_block(
            pool_records[3], owner_rewards=True)

        oversaturated_epoch = max(
            e for e, r in pool_records[2].saturation_amounts.items() if r < 0)
        saturated_epoch = oversaturated_epoch - 2
        nonsaturated_epoch = oversaturated_epoch - 4

        try:
            # check that rewards per block per stake for "pool2" in the epoch where the pool is
            # oversaturated is lower than in epochs where pools are not oversaturated
            assert (pool1_user_rewards_per_block[nonsaturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])
            assert (pool2_user_rewards_per_block[nonsaturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])
            assert (pool3_user_rewards_per_block[nonsaturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])

            assert (pool1_user_rewards_per_block[saturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])
            assert (pool2_user_rewards_per_block[saturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])
            assert (pool3_user_rewards_per_block[saturated_epoch] >
                    pool2_user_rewards_per_block[oversaturated_epoch])

            # check that oversaturated pool doesn't lead to increased rewards for pool owner
            # when compared to saturated pool, i.e. total pool margin amount is not increased
            pool1_rew_fraction_sat = pool1_owner_rewards_per_block[
                saturated_epoch]
            pool2_rew_fraction_sat = pool2_owner_rewards_per_block[
                saturated_epoch]
            pool3_rew_fraction_sat = pool3_owner_rewards_per_block[
                saturated_epoch]

            pool2_rew_fraction_over = pool2_owner_rewards_per_block[
                oversaturated_epoch]

            assert pool2_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval(
                pool2_rew_fraction_sat,
                pool2_rew_fraction_over,
                frac=0.4,
            )
            assert pool1_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval(
                pool1_rew_fraction_sat,
                pool2_rew_fraction_over,
                frac=0.4,
            )
            assert pool3_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval(
                pool3_rew_fraction_sat,
                pool2_rew_fraction_over,
                frac=0.4,
            )

            # Compare rewards in last (non-saturated) epoch to rewards in next-to-last
            # (saturated / over-saturated) epoch.
            # This way check that staked amount for each pool was restored to `initial_balance`
            # and that rewards correspond to the restored amounts.
            for pool_rec in pool_records.values():
                assert (pool_rec.user_rewards[-1].reward_per_epoch * 100 <
                        pool_rec.user_rewards[-2].reward_per_epoch)
        except Exception:
            # save debugging data in case of test failure
            with open(f"{temp_template}_pool_records.pickle",
                      "wb") as out_data:
                pickle.dump(pool_records, out_data)
            raise
    def test_pool_deregistration_fees(
        self,
        cluster: clusterlib.ClusterLib,
        temp_dir: Path,
        pool_users: List[clusterlib.PoolUser],
        addr_fee: Tuple[int, int],
    ):
        """Test pool deregistration fees."""
        no_of_addr, expected_fee = addr_fee
        rand_str = clusterlib.get_rand_str(4)
        temp_template = f"{helpers.get_func_name()}_{rand_str}_{no_of_addr}"
        src_address = pool_users[0].payment.address

        pool_name = f"pool_{rand_str}"
        pool_metadata = {
            "name": pool_name,
            "description": "Shelley QA E2E test Test",
            "ticker": "QA1",
            "homepage": "www.test1.com",
        }
        pool_metadata_file = helpers.write_json(
            temp_dir / f"{pool_name}_registration_metadata.json",
            pool_metadata)

        pool_data = clusterlib.PoolData(
            pool_name=pool_name,
            pool_pledge=222,
            pool_cost=123,
            pool_margin=0.512,
            pool_metadata_url="https://www.where_metadata_file_is_located.com",
            pool_metadata_hash=cluster.gen_pool_metadata_hash(
                pool_metadata_file),
        )

        # create pool owners
        selected_owners = pool_users[:no_of_addr]

        # create node cold key pair and counter
        node_cold = cluster.gen_cold_key_pair_and_counter(
            node_name=pool_data.pool_name)

        # create deregistration certificate
        pool_dereg_cert_file = cluster.gen_pool_deregistration_cert(
            pool_name=pool_data.pool_name,
            cold_vkey_file=node_cold.vkey_file,
            epoch=cluster.get_epoch() + 1,
        )

        tx_files = clusterlib.TxFiles(
            certificate_files=[pool_dereg_cert_file],
            signing_key_files=[
                *[p.payment.skey_file for p in selected_owners],
                *[p.stake.skey_file for p in selected_owners],
                node_cold.skey_file,
            ],
        )

        # calculate TX fee
        tx_fee = cluster.calculate_tx_fee(src_address=src_address,
                                          tx_name=temp_template,
                                          tx_files=tx_files)
        assert helpers.is_in_interval(
            tx_fee, expected_fee), "Expected fee doesn't match the actual fee"
示例#7
0
    def test_valid_policy_before(
            self, cluster: clusterlib.ClusterLib,
            issuers_addrs: List[clusterlib.AddressRecord]):
        """Test minting and burning tokens before given slot."""
        expected_fee = 351093

        temp_template = helpers.get_func_name()
        rand = clusterlib.get_rand_str(4)
        amount = 5

        token_mint_addr = issuers_addrs[0]
        payment_vkey_files = [p.vkey_file for p in issuers_addrs]

        before_slot = cluster.get_slot_no() + 10_000

        # create multisig script
        multisig_script = cluster.build_multisig_script(
            script_name=temp_template,
            script_type_arg=clusterlib.MultiSigTypeArgs.ALL,
            payment_vkey_files=payment_vkey_files[1:],
            slot=before_slot,
            slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE,
        )
        policyid = cluster.get_policyid(multisig_script)

        tokens_to_mint = []
        for tnum in range(5):
            asset_name = f"couttscoin{rand}{tnum}"
            token = f"{policyid}.{asset_name}"

            assert not cluster.get_utxo(token_mint_addr.address, coins=[
                token
            ]), "The token already exists"

            tokens_to_mint.append(
                clusterlib_utils.TokenRecord(
                    token=token,
                    asset_name=asset_name,
                    amount=amount,
                    issuers_addrs=issuers_addrs,
                    token_mint_addr=token_mint_addr,
                    script=multisig_script,
                ))

        # token minting
        tx_out_mint = _mint_or_burn_witness(
            cluster_obj=cluster,
            new_tokens=tokens_to_mint,
            temp_template=f"{temp_template}_mint",
            invalid_before=100,
            invalid_hereafter=cluster.get_slot_no() + 1000,
        )

        for t in tokens_to_mint:
            token_utxo = cluster.get_utxo(token_mint_addr.address,
                                          coins=[t.token])
            assert token_utxo and token_utxo[
                0].amount == amount, "The token was not minted"

        # token burning
        tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint]
        tx_out_burn = _mint_or_burn_witness(
            cluster_obj=cluster,
            new_tokens=tokens_to_burn,
            temp_template=f"{temp_template}_burn",
            invalid_before=100,
            invalid_hereafter=cluster.get_slot_no() + 1000,
        )

        for t in tokens_to_burn:
            token_utxo = cluster.get_utxo(token_mint_addr.address,
                                          coins=[t.token])
            assert not token_utxo, "The token was not burnt"

        # check expected fees
        assert helpers.is_in_interval(
            tx_out_mint.fee, expected_fee,
            frac=0.15) and helpers.is_in_interval(
                tx_out_burn.fee, expected_fee,
                frac=0.15), "TX fee doesn't fit the expected interval"
示例#8
0
    def test_minting_and_partial_burning(
            self, cluster: clusterlib.ClusterLib,
            issuers_addrs: List[clusterlib.AddressRecord]):
        """Test minting and partial burning of tokens."""
        expected_fee = 201141

        temp_template = helpers.get_func_name()
        asset_name = f"couttscoin{clusterlib.get_rand_str(4)}"
        amount = 50

        payment_vkey_files = [p.vkey_file for p in issuers_addrs]
        token_mint_addr = issuers_addrs[0]

        # create multisig script
        multisig_script = cluster.build_multisig_script(
            script_name=temp_template,
            script_type_arg=clusterlib.MultiSigTypeArgs.ALL,
            payment_vkey_files=payment_vkey_files[1:],
        )

        policyid = cluster.get_policyid(multisig_script)
        token = f"{policyid}.{asset_name}"

        assert not cluster.get_utxo(token_mint_addr.address,
                                    coins=[token]), "The token already exists"

        token_mint = clusterlib_utils.TokenRecord(
            token=token,
            asset_name=asset_name,
            amount=amount,
            issuers_addrs=issuers_addrs,
            token_mint_addr=token_mint_addr,
            script=multisig_script,
        )

        # token minting
        tx_out_mint = _mint_or_burn_witness(
            cluster_obj=cluster,
            new_tokens=[token_mint],
            temp_template=f"{temp_template}_mint",
        )

        token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token])
        assert token_utxo and token_utxo[
            0].amount == amount, "The token was not minted"

        # token burning
        burn_amount = amount - 10
        token_burn = token_mint._replace(amount=-burn_amount)
        _mint_or_burn_witness(
            cluster_obj=cluster,
            new_tokens=[token_burn],
            temp_template=f"{temp_template}_burn",
        )

        token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token])
        assert (token_utxo and token_utxo[0].amount
                == amount - burn_amount), "The token was not burned"

        # burn the rest of tokens
        final_burn = token_mint._replace(amount=-10)
        _mint_or_burn_witness(
            cluster_obj=cluster,
            new_tokens=[final_burn],
            temp_template=f"{temp_template}_burn",
        )

        # check expected fee
        assert helpers.is_in_interval(
            tx_out_mint.fee, expected_fee,
            frac=0.15), "TX fee doesn't fit the expected interval"
示例#9
0
    def test_multi_minting_and_burning_sign(
        self,
        cluster: clusterlib.ClusterLib,
        issuers_addrs: List[clusterlib.AddressRecord],
        simple_script_policyid: Tuple[Path, str],
        tokens_db: Tuple[int, int],
    ):
        """Test minting and burning multiple different tokens, sign the TX using skeys."""
        temp_template = helpers.get_func_name()
        rand = clusterlib.get_rand_str(8)
        amount = 5
        tokens_num, expected_fee = tokens_db

        token_mint_addr = issuers_addrs[0]
        issuer_addr = issuers_addrs[1]
        script, policyid = simple_script_policyid

        tokens_to_mint = []
        for tnum in range(tokens_num):
            asset_name = f"couttscoin{rand}{tnum}"
            token = f"{policyid}.{asset_name}"

            tokens_to_mint.append(
                clusterlib_utils.TokenRecord(
                    token=token,
                    asset_name=asset_name,
                    amount=amount,
                    issuers_addrs=[issuer_addr],
                    token_mint_addr=token_mint_addr,
                    script=script,
                ))

        # token minting
        minting_args = {
            "cluster_obj": cluster,
            "new_tokens": tokens_to_mint,
            "temp_template": f"{temp_template}_mint",
        }

        if tokens_num >= 500:
            with pytest.raises(clusterlib.CLIError) as excinfo:
                clusterlib_utils.mint_or_burn_sign(
                    **minting_args)  # type: ignore
            if tokens_num >= 1000:
                assert "(UtxoFailure (MaxTxSizeUTxO" in str(excinfo.value)
            else:
                assert "(UtxoFailure (OutputTooBigUTxO" in str(excinfo.value)
            return

        tx_out_mint = clusterlib_utils.mint_or_burn_sign(
            **minting_args)  # type: ignore

        for t in tokens_to_mint:
            token_utxo = cluster.get_utxo(token_mint_addr.address,
                                          coins=[t.token])
            assert token_utxo and token_utxo[
                0].amount == amount, "The token was not minted"

        # token burning
        tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint]
        tx_out_burn = clusterlib_utils.mint_or_burn_sign(
            cluster_obj=cluster,
            new_tokens=tokens_to_burn,
            temp_template=f"{temp_template}_burn",
        )

        for t in tokens_to_burn:
            token_utxo = cluster.get_utxo(token_mint_addr.address,
                                          coins=[t.token])
            assert not token_utxo, "The token was not burnt"

        # check expected fees
        assert helpers.is_in_interval(
            tx_out_mint.fee, expected_fee,
            frac=0.15) and helpers.is_in_interval(
                tx_out_burn.fee, expected_fee,
                frac=0.15), "TX fee doesn't fit the expected interval"
示例#10
0
    def test_minting_and_burning_sign(
            self, cluster: clusterlib.ClusterLib,
            issuers_addrs: List[clusterlib.AddressRecord]):
        """Test minting and burning of tokens, sign the transaction using skeys."""
        expected_fee = 188821

        temp_template = helpers.get_func_name()
        asset_name = f"couttscoin{clusterlib.get_rand_str(4)}"
        amount = 5

        token_mint_addr = issuers_addrs[0]
        issuer_addr = issuers_addrs[1]

        # create simple script
        keyhash = cluster.get_payment_vkey_hash(issuer_addr.vkey_file)
        script_content = {"keyHash": keyhash, "type": "sig"}
        script = Path(f"{temp_template}.script")
        with open(f"{temp_template}.script", "w") as out_json:
            json.dump(script_content, out_json)

        policyid = cluster.get_policyid(script)
        token = f"{policyid}.{asset_name}"

        assert not cluster.get_utxo(token_mint_addr.address,
                                    coins=[token]), "The token already exists"

        token_mint = clusterlib_utils.TokenRecord(
            token=token,
            asset_name=asset_name,
            amount=amount,
            issuers_addrs=[issuer_addr],
            token_mint_addr=token_mint_addr,
            script=script,
        )

        # token minting
        tx_out_mint = clusterlib_utils.mint_or_burn_sign(
            cluster_obj=cluster,
            new_tokens=[token_mint],
            temp_template=f"{temp_template}_mint",
        )

        token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token])
        assert token_utxo and token_utxo[
            0].amount == amount, "The token was not minted"

        # token burning
        token_burn = token_mint._replace(amount=-amount)
        tx_out_burn = clusterlib_utils.mint_or_burn_sign(
            cluster_obj=cluster,
            new_tokens=[token_burn],
            temp_template=f"{temp_template}_burn",
        )

        token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token])
        assert not token_utxo, "The token was not burnt"

        # check expected fees
        assert helpers.is_in_interval(
            tx_out_mint.fee, expected_fee,
            frac=0.15) and helpers.is_in_interval(
                tx_out_burn.fee, expected_fee,
                frac=0.15), "TX fee doesn't fit the expected interval"
    def test_witness_redeemer(
        self,
        cluster: clusterlib.ClusterLib,
        payment_addrs: List[clusterlib.AddressRecord],
        key: str,
    ):
        """Test minting a token with a Plutus script.

        Uses `cardano-cli transaction build` command for building the transactions.

        * fund the token issuer and create a UTxO for collateral
        * check that the expected amount was transferred to token issuer's address
        * mint the token using a Plutus script with required signer
        * check that the token was minted and collateral UTxO was not spent
        * check expected fees
        * check expected Plutus cost
        * (optional) check transactions in db-sync
        """
        # pylint: disable=too-many-locals
        temp_template = common.get_test_id(cluster)
        payment_addr = payment_addrs[0]
        issuer_addr = payment_addrs[1]

        lovelace_amount = 2_000_000
        token_amount = 5
        script_fund = 200_000_000

        minting_cost = plutus_common.compute_cost(
            execution_cost=plutus_common.MINTING_WITNESS_REDEEMER_COST,
            protocol_params=cluster.get_protocol_params(),
        )

        if key == "normal":
            redeemer_file = plutus_common.DATUM_WITNESS_GOLDEN_NORMAL
            signing_key_golden = plutus_common.SIGNING_KEY_GOLDEN
        else:
            redeemer_file = plutus_common.DATUM_WITNESS_GOLDEN_EXTENDED
            signing_key_golden = plutus_common.SIGNING_KEY_GOLDEN_EXTENDED

        issuer_init_balance = cluster.get_address_balance(issuer_addr.address)

        # Step 1: fund the token issuer

        mint_utxos, collateral_utxos, tx_output_step1 = _fund_issuer(
            cluster_obj=cluster,
            temp_template=temp_template,
            payment_addr=payment_addr,
            issuer_addr=issuer_addr,
            minting_cost=minting_cost,
            amount=script_fund,
        )

        # Step 2: mint the "qacoin"

        policyid = cluster.get_policyid(plutus_common.MINTING_PLUTUS_V1)
        asset_name = f"qacoin{clusterlib.get_rand_str(4)}".encode(
            "utf-8").hex()
        token = f"{policyid}.{asset_name}"
        mint_txouts = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=token_amount,
                             coin=token)
        ]

        plutus_mint_data = [
            clusterlib.Mint(
                txouts=mint_txouts,
                script_file=plutus_common.MINTING_PLUTUS_V1,
                collaterals=collateral_utxos,
                redeemer_file=redeemer_file,
            )
        ]

        tx_files_step2 = clusterlib.TxFiles(
            signing_key_files=[issuer_addr.skey_file, signing_key_golden], )
        txouts_step2 = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=lovelace_amount),
            *mint_txouts,
        ]
        tx_output_step2 = cluster.build_tx(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            required_signers=[signing_key_golden],
        )
        plutus_cost = cluster.calculate_plutus_script_cost(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            required_signers=[signing_key_golden],
        )
        # sign incrementally (just to check that it works)
        tx_signed_step2 = cluster.sign_tx(
            tx_body_file=tx_output_step2.out_file,
            signing_key_files=[issuer_addr.skey_file],
            tx_name=f"{temp_template}_step2_sign0",
        )
        tx_signed_step2_inc = cluster.sign_tx(
            tx_file=tx_signed_step2,
            signing_key_files=[signing_key_golden],
            tx_name=f"{temp_template}_step2_sign1",
        )
        cluster.submit_tx(tx_file=tx_signed_step2_inc, txins=mint_utxos)

        assert (
            cluster.get_address_balance(
                issuer_addr.address) == issuer_init_balance +
            minting_cost.collateral + lovelace_amount
        ), f"Incorrect balance for token issuer address `{issuer_addr.address}`"

        token_utxo = cluster.get_utxo(address=issuer_addr.address,
                                      coins=[token])
        assert token_utxo and token_utxo[
            0].amount == token_amount, "The token was not minted"

        # check expected fees
        expected_fee_step1 = 167_349
        assert helpers.is_in_interval(tx_output_step1.fee,
                                      expected_fee_step1,
                                      frac=0.15)

        expected_fee_step2 = 372_438
        assert helpers.is_in_interval(tx_output_step2.fee,
                                      expected_fee_step2,
                                      frac=0.15)

        plutus_common.check_plutus_cost(
            plutus_cost=plutus_cost,
            expected_cost=[plutus_common.MINTING_WITNESS_REDEEMER_COST],
        )

        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step1)
        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)
    def test_two_scripts_minting(
            self, cluster: clusterlib.ClusterLib,
            payment_addrs: List[clusterlib.AddressRecord]):
        """Test minting two tokens with two different Plutus scripts.

        Uses `cardano-cli transaction build` command for building the transactions.

        * fund the token issuer and create a UTxO for collaterals
        * check that the expected amount was transferred to token issuer's address
        * mint the tokens using two different Plutus scripts
        * check that the tokens were minted and collateral UTxOs were not spent
        * check transaction view output
        * check expected fees
        * check expected Plutus cost
        * (optional) check transactions in db-sync
        """
        # pylint: disable=too-many-locals,too-many-statements
        temp_template = common.get_test_id(cluster)
        payment_addr = payment_addrs[0]
        issuer_addr = payment_addrs[1]

        lovelace_amount = 2_000_000
        token_amount = 5
        script_fund = 500_000_000

        # this is higher than `plutus_common.MINTING*_COST`, because the script context has changed
        # to include more stuff
        minting_cost_two = plutus_common.ExecutionCost(per_time=408_545_501,
                                                       per_space=1_126_016,
                                                       fixed_cost=94_428)
        minting_time_range_cost_two = plutus_common.ExecutionCost(
            per_time=427_707_230, per_space=1_188_952, fixed_cost=99_441)

        protocol_params = cluster.get_protocol_params()
        minting_cost1 = plutus_common.compute_cost(
            execution_cost=minting_cost_two, protocol_params=protocol_params)
        minting_cost2 = plutus_common.compute_cost(
            execution_cost=minting_time_range_cost_two,
            protocol_params=protocol_params)

        issuer_init_balance = cluster.get_address_balance(issuer_addr.address)

        # Step 1: fund the token issuer

        tx_files_step1 = clusterlib.TxFiles(
            signing_key_files=[payment_addr.skey_file], )
        txouts_step1 = [
            clusterlib.TxOut(address=issuer_addr.address, amount=script_fund),
            # for collaterals
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=minting_cost1.collateral),
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=minting_cost2.collateral),
        ]
        tx_output_step1 = cluster.build_tx(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step1",
            tx_files=tx_files_step1,
            txouts=txouts_step1,
            fee_buffer=2_000_000,
            # don't join 'change' and 'collateral' txouts, we need separate UTxOs
            join_txouts=False,
        )
        tx_signed_step1 = cluster.sign_tx(
            tx_body_file=tx_output_step1.out_file,
            signing_key_files=tx_files_step1.signing_key_files,
            tx_name=f"{temp_template}_step1",
        )
        cluster.submit_tx(tx_file=tx_signed_step1, txins=tx_output_step1.txins)

        issuer_step1_balance = cluster.get_address_balance(issuer_addr.address)
        assert (
            issuer_step1_balance == issuer_init_balance + script_fund +
            minting_cost1.collateral + minting_cost2.collateral
        ), f"Incorrect balance for token issuer address `{issuer_addr.address}`"

        # Step 2: mint the "qacoins"

        txid_step1 = cluster.get_txid(tx_body_file=tx_output_step1.out_file)
        mint_utxos = cluster.get_utxo(txin=f"{txid_step1}#1")
        collateral_utxo1 = cluster.get_utxo(txin=f"{txid_step1}#2")
        collateral_utxo2 = cluster.get_utxo(txin=f"{txid_step1}#3")

        slot_step2 = cluster.get_slot_no()

        # "time range" qacoin
        slots_offset = 200
        timestamp_offset_ms = int(slots_offset * cluster.slot_length +
                                  5) * 1_000

        protocol_version = cluster.get_protocol_params(
        )["protocolVersion"]["major"]
        if protocol_version > 5:
            # POSIX timestamp + offset
            redeemer_value_timerange = (
                int(datetime.datetime.now().timestamp() * 1_000) +
                timestamp_offset_ms)
        else:
            # BUG: https://github.com/input-output-hk/cardano-node/issues/3090
            redeemer_value_timerange = 1_000_000_000_000

        policyid_timerange = cluster.get_policyid(
            plutus_common.MINTING_TIME_RANGE_PLUTUS_V1)
        asset_name_timerange = f"qacoint{clusterlib.get_rand_str(4)}".encode(
            "utf-8").hex()
        token_timerange = f"{policyid_timerange}.{asset_name_timerange}"
        mint_txouts_timerange = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=token_amount,
                             coin=token_timerange)
        ]

        # "anyone can mint" qacoin
        redeemer_cbor_file = plutus_common.REDEEMER_42_CBOR
        policyid_anyone = cluster.get_policyid(plutus_common.MINTING_PLUTUS_V1)
        asset_name_anyone = f"qacoina{clusterlib.get_rand_str(4)}".encode(
            "utf-8").hex()
        token_anyone = f"{policyid_anyone}.{asset_name_anyone}"
        mint_txouts_anyone = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=token_amount,
                             coin=token_anyone)
        ]

        # mint the tokens
        plutus_mint_data = [
            clusterlib.Mint(
                txouts=mint_txouts_timerange,
                script_file=plutus_common.MINTING_TIME_RANGE_PLUTUS_V1,
                collaterals=collateral_utxo1,
                redeemer_value=str(redeemer_value_timerange),
            ),
            clusterlib.Mint(
                txouts=mint_txouts_anyone,
                script_file=plutus_common.MINTING_PLUTUS_V1,
                collaterals=collateral_utxo2,
                redeemer_cbor_file=redeemer_cbor_file,
            ),
        ]

        tx_files_step2 = clusterlib.TxFiles(
            signing_key_files=[issuer_addr.skey_file], )
        txouts_step2 = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=lovelace_amount),
            *mint_txouts_timerange,
            *mint_txouts_anyone,
        ]
        tx_output_step2 = cluster.build_tx(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            invalid_before=slot_step2 - slots_offset,
            invalid_hereafter=slot_step2 + slots_offset,
        )
        plutus_cost = cluster.calculate_plutus_script_cost(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            invalid_before=slot_step2 - slots_offset,
            invalid_hereafter=slot_step2 + slots_offset,
        )
        tx_signed_step2 = cluster.sign_tx(
            tx_body_file=tx_output_step2.out_file,
            signing_key_files=tx_files_step2.signing_key_files,
            tx_name=f"{temp_template}_step2",
        )
        cluster.submit_tx(tx_file=tx_signed_step2, txins=mint_utxos)

        assert (
            cluster.get_address_balance(
                issuer_addr.address) == issuer_init_balance +
            minting_cost1.collateral + minting_cost2.collateral +
            lovelace_amount
        ), f"Incorrect balance for token issuer address `{issuer_addr.address}`"

        token_utxo_timerange = cluster.get_utxo(address=issuer_addr.address,
                                                coins=[token_timerange])
        assert (token_utxo_timerange and token_utxo_timerange[0].amount
                == token_amount), "The 'timerange' token was not minted"

        token_utxo_anyone = cluster.get_utxo(address=issuer_addr.address,
                                             coins=[token_anyone])
        assert (token_utxo_anyone and token_utxo_anyone[0].amount
                == token_amount), "The 'anyone' token was not minted"

        # check expected fees
        expected_fee_step1 = 168_977
        assert helpers.is_in_interval(tx_output_step1.fee,
                                      expected_fee_step1,
                                      frac=0.15)

        expected_fee_step2 = 633_269
        assert helpers.is_in_interval(tx_output_step2.fee,
                                      expected_fee_step2,
                                      frac=0.15)

        plutus_common.check_plutus_cost(
            plutus_cost=plutus_cost,
            expected_cost=[minting_cost_two, minting_time_range_cost_two],
        )

        # check tx_view
        tx_view.check_tx_view(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)

        # check transactions in db-sync
        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step1)
        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)
    def test_time_range_minting(self, cluster: clusterlib.ClusterLib,
                                payment_addrs: List[clusterlib.AddressRecord]):
        """Test minting a token with a time constraints Plutus script.

        Uses `cardano-cli transaction build` command for building the transactions.

        * fund the token issuer and create a UTxO for collateral
        * check that the expected amount was transferred to token issuer's address
        * mint the token using a Plutus script
        * check that the token was minted and collateral UTxO was not spent
        * check expected fees
        * check expected Plutus cost
        * (optional) check transactions in db-sync
        """
        # pylint: disable=too-many-locals
        temp_template = common.get_test_id(cluster)
        payment_addr = payment_addrs[0]
        issuer_addr = payment_addrs[1]

        lovelace_amount = 2_000_000
        token_amount = 5
        script_fund = 200_000_000

        minting_cost = plutus_common.compute_cost(
            execution_cost=plutus_common.MINTING_TIME_RANGE_COST,
            protocol_params=cluster.get_protocol_params(),
        )

        issuer_init_balance = cluster.get_address_balance(issuer_addr.address)

        # Step 1: fund the token issuer

        mint_utxos, collateral_utxos, tx_output_step1 = _fund_issuer(
            cluster_obj=cluster,
            temp_template=temp_template,
            payment_addr=payment_addr,
            issuer_addr=issuer_addr,
            minting_cost=minting_cost,
            amount=script_fund,
        )

        # Step 2: mint the "qacoin"

        slot_step2 = cluster.get_slot_no()
        slots_offset = 200
        timestamp_offset_ms = int(slots_offset * cluster.slot_length +
                                  5) * 1_000

        protocol_version = cluster.get_protocol_params(
        )["protocolVersion"]["major"]
        if protocol_version > 5:
            # POSIX timestamp + offset
            redeemer_value = int(datetime.datetime.now().timestamp() *
                                 1_000) + timestamp_offset_ms
        else:
            # BUG: https://github.com/input-output-hk/cardano-node/issues/3090
            redeemer_value = 1_000_000_000_000

        policyid = cluster.get_policyid(
            plutus_common.MINTING_TIME_RANGE_PLUTUS_V1)
        asset_name = f"qacoin{clusterlib.get_rand_str(4)}".encode(
            "utf-8").hex()
        token = f"{policyid}.{asset_name}"
        mint_txouts = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=token_amount,
                             coin=token)
        ]

        plutus_mint_data = [
            clusterlib.Mint(
                txouts=mint_txouts,
                script_file=plutus_common.MINTING_TIME_RANGE_PLUTUS_V1,
                collaterals=collateral_utxos,
                redeemer_value=str(redeemer_value),
            )
        ]

        tx_files_step2 = clusterlib.TxFiles(
            signing_key_files=[issuer_addr.skey_file], )
        txouts_step2 = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=lovelace_amount),
            *mint_txouts,
        ]
        tx_output_step2 = cluster.build_tx(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            invalid_before=slot_step2 - slots_offset,
            invalid_hereafter=slot_step2 + slots_offset,
        )
        plutus_cost = cluster.calculate_plutus_script_cost(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
            invalid_before=slot_step2 - slots_offset,
            invalid_hereafter=slot_step2 + slots_offset,
        )
        tx_signed_step2 = cluster.sign_tx(
            tx_body_file=tx_output_step2.out_file,
            signing_key_files=tx_files_step2.signing_key_files,
            tx_name=f"{temp_template}_step2",
        )
        cluster.submit_tx(tx_file=tx_signed_step2, txins=mint_utxos)

        assert (
            cluster.get_address_balance(
                issuer_addr.address) == issuer_init_balance +
            minting_cost.collateral + lovelace_amount
        ), f"Incorrect balance for token issuer address `{issuer_addr.address}`"

        token_utxo = cluster.get_utxo(address=issuer_addr.address,
                                      coins=[token])
        assert token_utxo and token_utxo[
            0].amount == token_amount, "The token was not minted"

        # check expected fees
        expected_fee_step1 = 167_349
        assert helpers.is_in_interval(tx_output_step1.fee,
                                      expected_fee_step1,
                                      frac=0.15)

        expected_fee_step2 = 411_175
        assert helpers.is_in_interval(tx_output_step2.fee,
                                      expected_fee_step2,
                                      frac=0.15)

        plutus_common.check_plutus_cost(
            plutus_cost=plutus_cost,
            expected_cost=[plutus_common.MINTING_TIME_RANGE_COST],
        )

        # check tx_view
        tx_view.check_tx_view(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)

        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step1)
        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)
    def test_minting_one_token(self, cluster: clusterlib.ClusterLib,
                               payment_addrs: List[clusterlib.AddressRecord]):
        """Test minting a token with a Plutus script.

        Uses `cardano-cli transaction build` command for building the transactions.

        * fund the token issuer and create a UTxO for collateral
        * check that the expected amount was transferred to token issuer's address
        * mint the token using a Plutus script
        * check that the token was minted and collateral UTxO was not spent
        * check expected fees
        * check expected Plutus cost
        * (optional) check transactions in db-sync
        """
        # pylint: disable=too-many-locals
        temp_template = common.get_test_id(cluster)
        payment_addr = payment_addrs[0]
        issuer_addr = payment_addrs[1]

        lovelace_amount = 2_000_000
        token_amount = 5
        script_fund = 200_000_000

        minting_cost = plutus_common.compute_cost(
            execution_cost=plutus_common.MINTING_COST,
            protocol_params=cluster.get_protocol_params())

        issuer_init_balance = cluster.get_address_balance(issuer_addr.address)

        # Step 1: fund the token issuer and create UTXO for collaterals

        mint_utxos, collateral_utxos, tx_output_step1 = _fund_issuer(
            cluster_obj=cluster,
            temp_template=temp_template,
            payment_addr=payment_addr,
            issuer_addr=issuer_addr,
            minting_cost=minting_cost,
            amount=script_fund,
            collateral_utxo_num=2,
        )

        # Step 2: mint the "qacoin"

        policyid = cluster.get_policyid(plutus_common.MINTING_PLUTUS_V1)
        asset_name = f"qacoin{clusterlib.get_rand_str(4)}".encode(
            "utf-8").hex()
        token = f"{policyid}.{asset_name}"
        mint_txouts = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=token_amount,
                             coin=token)
        ]

        plutus_mint_data = [
            clusterlib.Mint(
                txouts=mint_txouts,
                script_file=plutus_common.MINTING_PLUTUS_V1,
                collaterals=collateral_utxos,
                redeemer_file=plutus_common.REDEEMER_42,
            )
        ]

        tx_files_step2 = clusterlib.TxFiles(
            signing_key_files=[issuer_addr.skey_file], )
        txouts_step2 = [
            clusterlib.TxOut(address=issuer_addr.address,
                             amount=lovelace_amount),
            *mint_txouts,
        ]
        tx_output_step2 = cluster.build_tx(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
        )
        plutus_cost = cluster.calculate_plutus_script_cost(
            src_address=payment_addr.address,
            tx_name=f"{temp_template}_step2",
            tx_files=tx_files_step2,
            txins=mint_utxos,
            txouts=txouts_step2,
            mint=plutus_mint_data,
        )
        tx_signed_step2 = cluster.sign_tx(
            tx_body_file=tx_output_step2.out_file,
            signing_key_files=tx_files_step2.signing_key_files,
            tx_name=f"{temp_template}_step2",
        )
        cluster.submit_tx(tx_file=tx_signed_step2, txins=mint_utxos)

        assert (
            cluster.get_address_balance(
                issuer_addr.address) == issuer_init_balance +
            minting_cost.collateral + lovelace_amount
        ), f"Incorrect balance for token issuer address `{issuer_addr.address}`"

        token_utxo = cluster.get_utxo(address=issuer_addr.address,
                                      coins=[token])
        assert token_utxo and token_utxo[
            0].amount == token_amount, "The token was not minted"

        # check expected fees
        expected_fee_step1 = 168_977
        assert helpers.is_in_interval(tx_output_step1.fee,
                                      expected_fee_step1,
                                      frac=0.15)

        expected_fee_step2 = 371_111
        assert helpers.is_in_interval(tx_output_step2.fee,
                                      expected_fee_step2,
                                      frac=0.15)

        plutus_common.check_plutus_cost(
            plutus_cost=plutus_cost,
            expected_cost=[plutus_common.MINTING_COST],
        )

        # check tx_view
        tx_view.check_tx_view(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)

        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step1)
        dbsync_utils.check_tx(cluster_obj=cluster,
                              tx_raw_output=tx_output_step2)