def _fund_issuer( cluster_obj: clusterlib.ClusterLib, temp_template: str, payment_addr: clusterlib.AddressRecord, issuer_addr: clusterlib.AddressRecord, amount: int, collateral_amount: int, ) -> Tuple[List[clusterlib.UTXOData], List[clusterlib.UTXOData], clusterlib.TxRawOutput]: """Fund the token issuer.""" issuer_init_balance = cluster_obj.get_address_balance(issuer_addr.address) tx_files = clusterlib.TxFiles(signing_key_files=[payment_addr.skey_file], ) txouts = [ clusterlib.TxOut( address=issuer_addr.address, amount=amount, ), clusterlib.TxOut(address=issuer_addr.address, amount=collateral_amount), ] tx_output = cluster_obj.build_tx( src_address=payment_addr.address, tx_name=f"{temp_template}_step1", tx_files=tx_files, txouts=txouts, fee_buffer=2_000_000, # don't join 'change' and 'collateral' txouts, we need separate UTxOs join_txouts=False, ) tx_signed = cluster_obj.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_step1", ) cluster_obj.submit_tx(tx_file=tx_signed, txins=tx_output.txins) issuer_balance = cluster_obj.get_address_balance(issuer_addr.address) assert ( issuer_balance == issuer_init_balance + amount + collateral_amount ), f"Incorrect balance for token issuer address `{issuer_addr.address}`" txid = cluster_obj.get_txid(tx_body_file=tx_output.out_file) mint_utxos = cluster_obj.get_utxo(txin=f"{txid}#1") collateral_utxos = [ clusterlib.UTXOData(utxo_hash=txid, utxo_ix=2, amount=collateral_amount, address=issuer_addr.address) ] return mint_utxos, collateral_utxos, tx_output
def test_negative_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee: int, ): """Try to send a transaction with negative fee (property-based test). Expect failure. """ temp_template = f"{helpers.get_func_name()}_{helpers.get_timestamped_rand_str()}" src_address = payment_addrs[0].address dst_address = payment_addrs[1].address destinations = [clusterlib.TxOut(address=dst_address, amount=10)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=fee, ) assert "option --fee: cannot parse value" in str(excinfo.value)
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 return_funds_to_faucet( *src_addrs: clusterlib.AddressRecord, cluster_obj: clusterlib.ClusterLib, faucet_addr: str, amount: int = -1, tx_name: Optional[str] = None, destination_dir: FileType = ".", ) -> None: """Send `amount` from all `src_addrs` to `faucet_addr`. The amount of "-1" means all available funds. """ tx_name = tx_name or helpers.get_timestamped_rand_str() tx_name = f"{tx_name}_return_funds" with helpers.FileLockIfXdist(f"{helpers.get_basetemp()}/{faucet_addr}.lock"): try: logging.disable(logging.ERROR) for src in src_addrs: fund_dst = [clusterlib.TxOut(address=faucet_addr, amount=amount)] fund_tx_files = clusterlib.TxFiles(signing_key_files=[src.skey_file]) # try to return funds; don't mind if there's not enough funds for fees etc. try: cluster_obj.send_funds( src_address=src.address, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, destination_dir=destination_dir, ) except Exception: pass finally: logging.disable(logging.NOTSET)
def test_transfer_no_ada( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], new_token: clusterlib_utils.TokenRecord, ): """Try to create an UTxO with just native tokens, no ADA. Expect failure.""" temp_template = helpers.get_func_name() amount = 10 src_address = new_token.token_mint_addr.address dst_address = payment_addrs[2].address destinations = [ clusterlib.TxOut(address=dst_address, amount=amount, coin=new_token.token) ] tx_files = clusterlib.TxFiles( signing_key_files=[new_token.token_mint_addr.skey_file]) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, ) assert "OutputTooSmallUTxO" in str(excinfo.value)
def withdraw_reward( cluster_obj: clusterlib.ClusterLib, stake_addr_record: clusterlib.AddressRecord, dst_addr_record: clusterlib.AddressRecord, name_template: str, ) -> None: """Withdraw rewards to payment address.""" dst_address = dst_addr_record.address tx_files_withdrawal = clusterlib.TxFiles(signing_key_files=[ dst_addr_record.skey_file, stake_addr_record.skey_file ], ) LOGGER.info(f"Withdrawing rewards for '{stake_addr_record.address}'") try: cluster_obj.send_tx( src_address=dst_address, tx_name=f"rf_{name_template}_reward_withdrawal", tx_files=tx_files_withdrawal, withdrawals=[ clusterlib.TxOut(address=stake_addr_record.address, amount=-1) ], ) except clusterlib.CLIError: return
def mint_or_burn_sign( cluster_obj: clusterlib.ClusterLib, new_tokens: List[TokenRecord], temp_template: str, ) -> clusterlib.TxRawOutput: """Mint or burn tokens, depending on the `amount` value. Sign using skeys. Positive `amount` value means minting, negative means burning. """ _issuers_addrs = [n.issuers_addrs for n in new_tokens] issuers_addrs = list(itertools.chain.from_iterable(_issuers_addrs)) issuers_skey_files = [p.skey_file for p in issuers_addrs] token_mint_addr_skey_files = [ t.token_mint_addr.skey_file for t in new_tokens ] src_address = new_tokens[0].token_mint_addr.address tx_files = clusterlib.TxFiles( signing_key_files=[*issuers_skey_files, *token_mint_addr_skey_files]) # build and sign a transaction ttl = cluster_obj.calculate_tx_ttl() fee = cluster_obj.calculate_tx_fee( src_address=src_address, tx_name=temp_template, tx_files=tx_files, ttl=ttl, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=len(issuers_skey_files) * 2, ) tx_raw_output = cluster_obj.build_raw_tx( src_address=src_address, tx_name=temp_template, tx_files=tx_files, fee=fee, ttl=ttl, mint=[ clusterlib.TxOut(address=n.token_mint_addr.address, amount=n.amount, coin=n.token) for n in new_tokens ], ) out_file_signed = cluster_obj.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=temp_template, script_files=[n.script for n in new_tokens], ) # submit signed transaction cluster_obj.submit_tx(out_file_signed) cluster_obj.wait_for_new_block(new_blocks=2) return tx_raw_output
def test_normal_tx_to_script_addr( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send funds to script address using TX signed with skeys (not using witness files).""" temp_template = helpers.get_func_name() src_address = payment_addrs[0].address amount = 1000 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=[p.vkey_file for p in payment_addrs], ) # create script address script_address = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # record initial balances src_init_balance = cluster.get_address_balance(src_address) dst_init_balance = cluster.get_address_balance(script_address) # send funds to script address destinations = [ clusterlib.TxOut(address=script_address, amount=amount) ] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) tx_raw_output = cluster.send_funds( src_address=src_address, tx_name=temp_template, destinations=destinations, tx_files=tx_files, ) cluster.wait_for_new_block(new_blocks=2) # check final balances assert ( cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - amount), f"Incorrect balance for source address `{src_address}`" assert ( cluster.get_address_balance(script_address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{script_address}`"
def test_expected_or_higher_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee_add: int, ): """Send a transaction with fee that is same or higher than expected.""" temp_template = f"{common.get_test_id(cluster)}_{fee_add}" amount = 2_000_000 src_address = payment_addrs[0].address dst_address = payment_addrs[1].address src_init_balance = cluster.get_address_balance(src_address) dst_init_balance = cluster.get_address_balance(dst_address) destinations = [clusterlib.TxOut(address=dst_address, amount=amount)] tx_files = clusterlib.TxFiles(signing_key_files=[payment_addrs[0].skey_file]) fee = ( cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) + fee_add ) tx_raw_output = cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=fee, ) assert tx_raw_output.fee == fee, "The actual fee doesn't match the specified fee" assert ( cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - len(destinations) * amount ), f"Incorrect balance for source address `{src_address}`" assert ( cluster.get_address_balance(dst_address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{dst_address}`"
def test_shelley_cddl(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Check expected failure when Shelley Tx is used with CDDL format.""" temp_template = common.get_test_id(cluster) src_address = payment_addrs[0].address dst_address = payment_addrs[1].address # amount value -1 means all available funds destinations = [clusterlib.TxOut(address=dst_address, amount=-1)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file]) fee = cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) orig_cddl_value = cluster.use_cddl try: cluster.use_cddl = True tx_raw_output = cluster.build_raw_tx( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, fee=fee, ) finally: cluster.use_cddl = orig_cddl_value with pytest.raises(clusterlib.CLIError) as excinfo: cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=temp_template, ) if "TextEnvelope error" in str(excinfo.value): pytest.xfail("TextEnvelope error") else: pytest.fail(f"Unexpected error:\n{excinfo.value}")
def return_funds_to_faucet( cluster_obj: clusterlib.ClusterLib, src_addr: clusterlib.AddressRecord, faucet_address: str, tx_name: str, ) -> None: """Send funds from `src_addr` to `faucet_address`.""" tx_name = f"rf_{tx_name}_return_funds" # the amount of "-1" means all available funds. fund_dst = [clusterlib.TxOut(address=faucet_address, amount=-1)] fund_tx_files = clusterlib.TxFiles(signing_key_files=[src_addr.skey_file]) txins = cluster_obj.get_utxo(address=src_addr.address, coins=[clusterlib.DEFAULT_COIN]) utxos_balance = functools.reduce(lambda x, y: x + y.amount, txins, 0) # skip if there no (or too little) Lovelace if utxos_balance < 1000_000: return # if the balance is too low, add a faucet UTxO so there's enough funds for fee # and the total amount is higher than min ADA value if utxos_balance < 3000_000: faucet_utxos = cluster_obj.get_utxo(address=faucet_address, coins=[clusterlib.DEFAULT_COIN]) futxo = random.choice(faucet_utxos) txins.append(futxo) LOGGER.info(f"Returning funds from '{src_addr.address}'") # try to return funds; don't mind if there's not enough funds for fees etc. with contextlib.suppress(clusterlib.CLIError): cluster_obj.send_tx( src_address=src_addr.address, tx_name=tx_name, txins=txins, txouts=fund_dst, tx_files=fund_tx_files, verify_tx=False, )
def _mint_tx( self, cluster_obj: clusterlib.ClusterLib, new_tokens: List[clusterlib_utils.TokenRecord], temp_template: str, ) -> Path: """Return signed TX for minting new token. Sign using skeys.""" _issuers_addrs = [n.issuers_addrs for n in new_tokens] issuers_addrs = list(itertools.chain.from_iterable(_issuers_addrs)) issuers_skey_files = [p.skey_file for p in issuers_addrs] token_mint_addr_skey_files = [ t.token_mint_addr.skey_file for t in new_tokens ] src_address = new_tokens[0].token_mint_addr.address tx_files = clusterlib.TxFiles(signing_key_files=[ *issuers_skey_files, *token_mint_addr_skey_files ]) # build and sign a transaction tx_raw_output = cluster_obj.build_raw_tx( src_address=src_address, tx_name=temp_template, tx_files=tx_files, fee=100_000, mint=[ clusterlib.TxOut(address=n.token_mint_addr.address, amount=n.amount, coin=n.token) for n in new_tokens ], ) out_file_signed = cluster_obj.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=temp_template, script_files=[n.script for n in new_tokens], ) return out_file_signed
def test_smaller_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee_change: float, ): """Try to send a transaction with smaller-than-expected fee. Expect failure. """ temp_template = f"{common.get_test_id(cluster)}_{fee_change}" src_address = payment_addrs[0].address dst_address = payment_addrs[1].address destinations = [clusterlib.TxOut(address=dst_address, amount=10)] tx_files = clusterlib.TxFiles(signing_key_files=[payment_addrs[0].skey_file]) fee = 0.0 if fee_change: fee = ( cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) / fee_change ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=int(fee), ) assert "FeeTooSmallUTxO" in str(excinfo.value)
def return_funds_to_faucet( *src_addrs: clusterlib.AddressRecord, cluster_obj: clusterlib.ClusterLib, faucet_addr: str, amount: Union[int, List[int]] = -1, tx_name: Optional[str] = None, destination_dir: FileType = ".", ) -> None: """Send `amount` from all `src_addrs` to `faucet_addr`. The amount of "-1" means all available funds. """ tx_name = tx_name or helpers.get_timestamped_rand_str() tx_name = f"{tx_name}_return_funds" if isinstance(amount, int): amount = [amount] * len(src_addrs) with locking.FileLockIfXdist( f"{temptools.get_basetemp()}/{faucet_addr}.lock"): try: logging.disable(logging.ERROR) for addr, amount_rec in zip(src_addrs, amount): fund_dst = [ clusterlib.TxOut(address=faucet_addr, amount=amount_rec) ] fund_tx_files = clusterlib.TxFiles( signing_key_files=[addr.skey_file]) # try to return funds; don't mind if there's not enough funds for fees etc. with contextlib.suppress(Exception): cluster_obj.send_funds( src_address=addr.address, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, destination_dir=destination_dir, ) finally: logging.disable(logging.NOTSET)
def _withdraw_rewards( *pool_users: clusterlib.PoolUser, cluster_obj: clusterlib.ClusterLib, tx_name: str, ) -> clusterlib.TxRawOutput: """Withdraw rewards from multiple stake addresses to corresponding payment addresses.""" src_addr = pool_users[0].payment tx_files_withdrawal = clusterlib.TxFiles(signing_key_files=[ src_addr.skey_file, *[p.stake.skey_file for p in pool_users] ], ) tx_raw_withdrawal_output = cluster_obj.send_tx( src_address=src_addr.address, tx_name=f"{tx_name}_reward_withdrawals", tx_files=tx_files_withdrawal, withdrawals=[ clusterlib.TxOut(address=p.stake.address, amount=-1) for p in pool_users ], ) return tx_raw_withdrawal_output
def return_funds_to_faucet( cluster_obj: clusterlib.ClusterLib, src_addr: clusterlib.AddressRecord, faucet_addr: str, tx_name: str, ) -> None: """Send funds from `src_addr` to `faucet_addr`.""" tx_name = f"rf_{tx_name}_return_funds" # the amount of "-1" means all available funds. fund_dst = [clusterlib.TxOut(address=faucet_addr, amount=-1)] fund_tx_files = clusterlib.TxFiles(signing_key_files=[src_addr.skey_file]) LOGGER.info(f"Returning funds from '{src_addr.address}'") # try to return funds; don't mind if there's not enough funds for fees etc. try: cluster_obj.send_funds( src_address=src_addr.address, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, ) except clusterlib.CLIError: pass
def test_lock_tx_datum_as_witness( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord] ): """Test unlock a Tx output with a datum as witness. Expect failure. """ __: Any # mypy workaround temp_template = common.get_test_id(cluster) amount = 2_000_000 plutus_op = PLUTUS_OP_ALWAYS_SUCCEEDS # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) script_utxos, collateral_utxos, __, __ = _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=True, ) plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, script_file=plutus_op.script_file, collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), redeemer_cbor_file=plutus_op.redeemer_cbor_file, datum_file=plutus_op.datum_file, ) ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.submit_tx( tx_file=tx_signed_redeem, txins=[t.txins[0] for t in tx_output_redeem.script_txins if t.txins], ) err_str = str(excinfo.value) assert "NonOutputSupplimentaryDatums" in err_str, err_str
def test_txout_locking( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], use_inline_datum: bool, use_reference_script: bool, request: FixtureRequest, ): """Test combinations of inline datum and datum file + reference script and script file. * create the necessary Tx outputs * check that the expected amount was locked at the script address * spend the locked UTxO * check that the expected UTxOs were correctly spent """ temp_template = f"{common.get_test_id(cluster)}_{request.node.callspec.id}" amount = 2_000_000 plutus_op = PLUTUS_OP_ALWAYS_SUCCEEDS # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) # Step 1: fund the Plutus script script_utxos, collateral_utxos, reference_utxos, __ = _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=use_inline_datum, use_reference_script=use_reference_script, ) plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, script_file=plutus_op.script_file if not use_reference_script else "", reference_txin=reference_utxos[0] if use_reference_script else None, reference_type=clusterlib.ScriptTypes.PLUTUS_V2 if use_reference_script else "", collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), redeemer_cbor_file=plutus_op.redeemer_cbor_file, inline_datum_present=use_inline_datum, datum_file=plutus_op.datum_file if not use_inline_datum else "", ) ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) dst_init_balance = cluster.get_address_balance(payment_addrs[1].address) cluster.submit_tx( tx_file=tx_signed_redeem, txins=[t.txins[0] for t in tx_output_redeem.script_txins if t.txins], ) assert ( cluster.get_address_balance(payment_addrs[1].address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{payment_addrs[1].address}`" script_utxos_lovelace = [u for u in script_utxos if u.coin == clusterlib.DEFAULT_COIN] for u in script_utxos_lovelace: assert not cluster.get_utxo( txin=f"{u.utxo_hash}#{u.utxo_ix}", coins=[clusterlib.DEFAULT_COIN] ), f"Inputs were NOT spent for `{u.address}`" if use_reference_script: assert cluster.get_utxo( txin=f"{reference_utxos[0].utxo_hash}#{reference_utxos[0].utxo_ix}", coins=[clusterlib.DEFAULT_COIN], ), "Reference input was spent"
def utxodata2txout(utxodata: clusterlib.UTXOData) -> clusterlib.TxOut: """Convert `clusterlib.UTXOData` to `clusterlib.TxOut`.""" return clusterlib.TxOut(address=utxodata.address, amount=utxodata.amount, coin=utxodata.coin)
def mint_or_burn_witness( cluster_obj: clusterlib.ClusterLib, new_tokens: List[TokenRecord], temp_template: str, invalid_hereafter: Optional[int] = None, invalid_before: Optional[int] = None, ) -> clusterlib.TxRawOutput: """Mint or burn tokens, depending on the `amount` value. Sign using witnesses. Positive `amount` value means minting, negative means burning. """ _issuers_addrs = [t.issuers_addrs for t in new_tokens] issuers_addrs = set(itertools.chain.from_iterable(_issuers_addrs)) issuers_skey_files = {p.skey_file for p in issuers_addrs} src_address = new_tokens[0].token_mint_addr.address # create TX body tx_files = clusterlib.TxFiles( script_files=clusterlib.ScriptFiles(minting_scripts=[t.script for t in new_tokens]), ) mint = [ clusterlib.TxOut(address=t.token_mint_addr.address, amount=t.amount, coin=t.token) for t in new_tokens ] fee = cluster_obj.calculate_tx_fee( src_address=src_address, tx_name=temp_template, tx_files=tx_files, mint=mint, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=len(issuers_skey_files), ) tx_raw_output = cluster_obj.build_raw_tx( src_address=src_address, tx_name=temp_template, tx_files=tx_files, fee=fee, invalid_hereafter=invalid_hereafter, invalid_before=invalid_before, mint=mint, ) # create witness file for each required key witness_files = [ cluster_obj.witness_tx( tx_body_file=tx_raw_output.out_file, witness_name=f"{temp_template}_skey{idx}", signing_key_files=[skey], ) for idx, skey in enumerate(issuers_skey_files) ] # sign TX using witness files tx_witnessed_file = cluster_obj.assemble_tx( tx_body_file=tx_raw_output.out_file, witness_files=witness_files, tx_name=temp_template, ) # submit signed TX cluster_obj.submit_tx(tx_file=tx_witnessed_file, txins=tx_raw_output.txins) return tx_raw_output
tx_name=f"{name_template}_dereg_stake_addr", tx_files=tx_files_deregister, ) return tx_raw_output def fund_from_genesis( *dst_addrs: str, cluster_obj: clusterlib.ClusterLib, amount: int = 2_000_000, tx_name: Optional[str] = None, destination_dir: FileType = ".", ) -> None: """Send `amount` from genesis addr to all `dst_addrs`.""" fund_dst = [ clusterlib.TxOut(address=d, amount=amount) for d in dst_addrs if cluster_obj.get_address_balance(d) < amount ] if not fund_dst: return with helpers.FileLockIfXdist(f"{helpers.get_basetemp()}/{cluster_obj.genesis_utxo_addr}.lock"): tx_name = tx_name or helpers.get_timestamped_rand_str() tx_name = f"{tx_name}_genesis_funding" fund_tx_files = clusterlib.TxFiles( signing_key_files=[ *cluster_obj.genesis_keys.delegate_skeys, cluster_obj.genesis_keys.genesis_utxo_skey, ] )
def test_deregister( self, cluster_manager: cluster_management.ClusterManager, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], ): """Deregister stake address. * create two payment addresses that share single stake address * register and delegate the stake address to pool * attempt to deregister the stake address - deregistration is expected to fail because there are rewards in the stake address * withdraw rewards to payment address and deregister stake address * check that the key deposit was returned and rewards withdrawn * check that the stake address is no longer delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) # create two payment addresses that share single stake address (just to test that # delegation works as expected even under such circumstances) stake_addr_rec = clusterlib_utils.create_stake_addr_records( f"{temp_template}_addr0", cluster_obj=cluster)[0] payment_addr_recs = clusterlib_utils.create_payment_addr_records( f"{temp_template}_addr0", f"{temp_template}_addr1", cluster_obj=cluster, stake_vkey_file=stake_addr_rec.vkey_file, ) # fund payment address clusterlib_utils.fund_from_faucet( *payment_addr_recs, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) pool_user = clusterlib.PoolUser(payment=payment_addr_recs[1], stake=stake_addr_rec) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_user=pool_user, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" tx_db_deleg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) if tx_db_deleg: # check in db-sync that both payment addresses share single stake address assert (dbsync_utils.get_utxo( address=payment_addr_recs[0].address).stake_address == stake_addr_rec.address) assert (dbsync_utils.get_utxo( address=payment_addr_recs[1].address).stake_address == stake_addr_rec.address) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_deleg, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) src_address = delegation_out.pool_user.payment.address LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_id}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # files for deregistering stake address stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ delegation_out.pool_user.payment.skey_file, delegation_out.pool_user.stake.skey_file, ], ) # attempt to deregister the stake address - deregistration is expected to fail # because there are rewards in the stake address with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_dereg_fail", tx_files=tx_files_deregister, ) assert "StakeKeyNonZeroAccountBalanceDELEG" in str(excinfo.value) src_payment_balance = cluster.get_address_balance(src_address) reward_balance = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance # withdraw rewards to payment address, deregister stake address tx_raw_deregister_output = cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_dereg_withdraw", tx_files=tx_files_deregister, withdrawals=[ clusterlib.TxOut( address=delegation_out.pool_user.stake.address, amount=-1) ], ) # check that the key deposit was returned and rewards withdrawn assert ( cluster.get_address_balance(src_address) == src_payment_balance - tx_raw_deregister_output.fee + reward_balance + cluster.get_address_deposit() ), f"Incorrect balance for source address `{src_address}`" # check that the stake address is no longer delegated stake_addr_info = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address) assert (not stake_addr_info.delegation ), f"Stake address is still delegated: {stake_addr_info}" tx_db_dereg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=tx_raw_deregister_output) if tx_db_dereg: assert delegation_out.pool_user.stake.address in tx_db_dereg.stake_deregistration assert ( cluster.get_address_balance(src_address) == dbsync_utils.get_utxo(address=src_address).amount_sum ), f"Unexpected balance for source address `{src_address}` in db-sync"
def _deploy_lobster_nft( cluster_obj: clusterlib.ClusterLib, temp_template: str, issuer_addr: clusterlib.AddressRecord, token_utxos: List[clusterlib.UTXOData], lobster_nft_token: str, nft_amount: int, lovelace_amount: int, ) -> Tuple[str, List[clusterlib.UTXOData], clusterlib.TxRawOutput]: """Deploy the LobsterNFT token to script address.""" script_address = cluster_obj.gen_payment_addr( addr_name=f"{temp_template}_deploy_nft", payment_script_file=LOBSTER_PLUTUS) tx_files = clusterlib.TxFiles(signing_key_files=[issuer_addr.skey_file], ) txouts = [ clusterlib.TxOut(address=script_address, amount=lovelace_amount, datum_hash=LOBSTER_DATUM_HASH), clusterlib.TxOut( address=script_address, amount=nft_amount, coin=lobster_nft_token, datum_hash=LOBSTER_DATUM_HASH, ), ] funds_txin = cluster_obj.get_utxo_with_highest_amount( address=issuer_addr.address) tx_output = cluster_obj.build_tx( src_address=issuer_addr.address, tx_name=f"{temp_template}_deploy_nft", tx_files=tx_files, txins=[*token_utxos, funds_txin], txouts=txouts, ) tx_signed = cluster_obj.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_deploy_nft", ) cluster_obj.submit_tx(tx_file=tx_signed, txins=token_utxos) txid = cluster_obj.get_txid(tx_body_file=tx_output.out_file) deployed_token_utxos = cluster_obj.get_utxo(txin=f"{txid}#1") # check expected balances token_utxo_lovelace = [ u for u in deployed_token_utxos if u.coin == clusterlib.DEFAULT_COIN ][0] assert ( token_utxo_lovelace.amount == lovelace_amount ), f"Incorrect Lovelace balance for script address address `{script_address}`" token_utxo_lobster = [ u for u in deployed_token_utxos if u.coin == lobster_nft_token ][0] assert ( token_utxo_lobster.amount == nft_amount ), f"Incorrect token balance for token issuer address `{script_address}`" return script_address, deployed_token_utxos, tx_output
def _mint_lobster_nft( cluster_obj: clusterlib.ClusterLib, temp_template: str, issuer_addr: clusterlib.AddressRecord, mint_utxos: List[clusterlib.UTXOData], collateral_utxos: List[clusterlib.UTXOData], nft_amount: int, lovelace_amount: int, ) -> Tuple[str, List[clusterlib.UTXOData], clusterlib.TxRawOutput]: """Mint the LobsterNFT token.""" lobster_policyid = cluster_obj.get_policyid(NFT_MINT_PLUTUS) asset_name = b"LobsterNFT".hex() lobster_nft_token = f"{lobster_policyid}.{asset_name}" mint_txouts = [ clusterlib.TxOut( address=issuer_addr.address, amount=nft_amount, coin=lobster_nft_token, ) ] plutus_mint_data = [ clusterlib.Mint( txouts=mint_txouts, script_file=NFT_MINT_PLUTUS, collaterals=collateral_utxos, redeemer_value="[]", ) ] tx_files = clusterlib.TxFiles(signing_key_files=[issuer_addr.skey_file], ) txouts = [ clusterlib.TxOut(address=issuer_addr.address, amount=lovelace_amount), *mint_txouts, ] tx_output = cluster_obj.build_tx( src_address=issuer_addr.address, tx_name=f"{temp_template}_mint_nft", tx_files=tx_files, txins=mint_utxos, txouts=txouts, mint=plutus_mint_data, ) tx_signed = cluster_obj.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_mint_nft", ) cluster_obj.submit_tx(tx_file=tx_signed, txins=mint_utxos) txid = cluster_obj.get_txid(tx_body_file=tx_output.out_file) token_utxos = cluster_obj.get_utxo(txin=f"{txid}#1") # check expected balances token_utxo_lovelace = [ u for u in token_utxos if u.coin == clusterlib.DEFAULT_COIN ][0] assert ( token_utxo_lovelace.amount == lovelace_amount ), f"Incorrect Lovelace balance for token issuer address `{issuer_addr.address}`" token_utxo_lobster = [ u for u in token_utxos if u.coin == lobster_nft_token ][0] assert ( token_utxo_lobster.amount == nft_amount ), f"Incorrect token balance for token issuer address `{issuer_addr.address}`" return lobster_nft_token, token_utxos, tx_output
def test_reference_locking( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], ): """Test locking a Tx output with a Plutus script and spending the locked UTxO. * create a Tx output with an inline datum at the script address * create a Tx output with a reference script * check that the expected amount was locked at the script address * spend the locked UTxO using the reference UTxO * check that the expected amount was spent * check that the reference UTxO was not spent """ temp_template = common.get_test_id(cluster) amount = 2_000_000 protocol_params = cluster.get_protocol_params() plutus_op = plutus_common.PlutusOp( script_file=plutus_common.ALWAYS_SUCCEEDS_PLUTUS_V2, datum_file=plutus_common.DATUM_42_TYPED, redeemer_cbor_file=plutus_common.REDEEMER_42_CBOR, execution_cost=plutus_common.ALWAYS_SUCCEEDS_V2_COST, ) # Step 1: fund the Plutus script # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file script_address = cluster.gen_payment_addr( addr_name=f"{temp_template}_addr", payment_script_file=plutus_op.script_file) redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=protocol_params) # create a Tx output with a datum hash at the script address tx_files_fund = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], ) txouts_fund = [ clusterlib.TxOut( address=script_address, amount=amount + redeem_cost.fee + FEE_REDEEM_TXSIZE, inline_datum_file=plutus_op.datum_file, ), # for reference script clusterlib.TxOut( address=payment_addrs[1].address, amount=2_000_000, reference_script_file=plutus_op.script_file, ), # for collateral clusterlib.TxOut(address=payment_addrs[1].address, amount=redeem_cost.collateral), ] tx_output_fund = cluster.send_tx( src_address=payment_addrs[0].address, tx_name=f"{temp_template}_step1", txouts=txouts_fund, tx_files=tx_files_fund, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=2, join_txouts=False, ) txid_fund = cluster.get_txid(tx_body_file=tx_output_fund.out_file) script_utxos = cluster.get_utxo(txin=f"{txid_fund}#0", coins=[clusterlib.DEFAULT_COIN]) reference_utxos = cluster.get_utxo(txin=f"{txid_fund}#1") collateral_utxos = cluster.get_utxo(txin=f"{txid_fund}#2") assert script_utxos, "No script UTxO" assert reference_utxos, "No reference UTxO" assert collateral_utxos, "No collateral UTxO" assert ( script_utxos[0].amount == amount + redeem_cost.fee + FEE_REDEEM_TXSIZE ), f"Incorrect balance for script address `{script_utxos[0].address}`" assert (script_utxos[0].inline_datum_hash and script_utxos[0].inline_datum ), "Inline datum not present on script UTxO" assert reference_utxos[ 0].reference_script, "Reference script not present on reference UTxO" # Step 2: spend the "locked" UTxO # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file reference_utxo = reference_utxos[0] plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, reference_txin=reference_utxo, reference_type=clusterlib.ScriptTypes.PLUTUS_V2, collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), inline_datum_present=True, redeemer_cbor_file=plutus_op.redeemer_cbor_file, ), ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) dst_init_balance = cluster.get_address_balance( payment_addrs[1].address) cluster.submit_tx( tx_file=tx_signed_redeem, txins=[ t.txins[0] for t in tx_output_redeem.script_txins if t.txins ], ) assert ( cluster.get_address_balance( payment_addrs[1].address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{payment_addrs[1].address}`" script_utxos_lovelace = [ u for u in script_utxos if u.coin == clusterlib.DEFAULT_COIN ] for u in script_utxos_lovelace: assert not cluster.get_utxo( txin=f"{u.utxo_hash}#{u.utxo_ix}", coins=[clusterlib.DEFAULT_COIN ]), f"Inputs were NOT spent for `{u.address}`" assert cluster.get_utxo( txin=f"{reference_utxo.utxo_hash}#{reference_utxo.utxo_ix}", coins=[clusterlib.DEFAULT_COIN], ), "Reference input was spent"
tx_files=tx_files_deregister, ) cluster_obj.wait_for_new_block(new_blocks=2) return tx_raw_output def fund_from_genesis( *dst_addrs: str, cluster_obj: clusterlib.ClusterLib, amount: int = 2_000_000, tx_name: Optional[str] = None, destination_dir: FileType = ".", ) -> None: """Send `amount` from genesis addr to all `dst_addrs`.""" fund_dst = [ clusterlib.TxOut(address=d, amount=amount) for d in dst_addrs if cluster_obj.get_address_balance(d) < amount ] if not fund_dst: return with helpers.FileLockIfXdist( f"{helpers.get_basetemp()}/{cluster_obj.genesis_utxo_addr}.lock"): tx_name = tx_name or helpers.get_timestamped_rand_str() tx_name = f"{tx_name}_genesis_funding" fund_tx_files = clusterlib.TxFiles(signing_key_files=[ *cluster_obj.genesis_keys.delegate_skeys, cluster_obj.genesis_keys.genesis_utxo_skey, ]) cluster_obj.send_funds(
def _fund_script( temp_template: str, cluster: clusterlib.ClusterLib, payment_addr: clusterlib.AddressRecord, dst_addr: clusterlib.AddressRecord, plutus_op: plutus_common.PlutusOp, amount: int, redeem_cost: plutus_common.ScriptCost, use_reference_script: Optional[bool] = False, use_inline_datum: Optional[bool] = False, ) -> Tuple[ List[clusterlib.UTXOData], List[clusterlib.UTXOData], List[clusterlib.UTXOData], clusterlib.TxRawOutput, ]: """Fund a Plutus script and create the locked UTxO, collateral UTxO and reference script.""" script_address = cluster.gen_payment_addr( addr_name=temp_template, payment_script_file=plutus_op.script_file ) # create a Tx output with a datum hash at the script address tx_files = clusterlib.TxFiles( signing_key_files=[payment_addr.skey_file], ) txouts = [ clusterlib.TxOut( address=script_address, amount=amount + redeem_cost.fee + FEE_REDEEM_TXSIZE, inline_datum_file=( plutus_op.datum_file if plutus_op.datum_file and use_inline_datum else "" ), inline_datum_value=( plutus_op.datum_value if plutus_op.datum_value and use_inline_datum else "" ), datum_hash_file=( plutus_op.datum_file if plutus_op.datum_file and not use_inline_datum else "" ), datum_hash_value=( plutus_op.datum_value if plutus_op.datum_value and not use_inline_datum else "" ), ), # for collateral clusterlib.TxOut(address=dst_addr.address, amount=redeem_cost.collateral), ] # for reference script if use_reference_script: txouts.append( clusterlib.TxOut( address=dst_addr.address, amount=amount, reference_script_file=plutus_op.script_file, ) ) tx_raw_output = cluster.send_tx( src_address=payment_addr.address, tx_name=f"{temp_template}_step1", txouts=txouts, tx_files=tx_files, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=2, join_txouts=False, ) txid = cluster.get_txid(tx_body_file=tx_raw_output.out_file) script_utxos = cluster.get_utxo(txin=f"{txid}#0") assert script_utxos, "No script UTxO" collateral_utxos = cluster.get_utxo(txin=f"{txid}#1") assert collateral_utxos, "No collateral UTxO" reference_utxos = [] if use_reference_script: reference_utxos = cluster.get_utxo(txin=f"{txid}#2") assert reference_utxos, "No reference script UTxO" return script_utxos, collateral_utxos, reference_utxos, tx_raw_output
def test_undelegate( self, cluster_manager: cluster_management.ClusterManager, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], ): """Undelegate stake address. * submit registration certificate and delegate to pool * wait for first reward * undelegate stake address: - withdraw rewards to payment address - deregister stake address - re-register stake address * check that the key deposit was not returned * check that rewards were withdrawn * check that the stake address is still registered * check that the stake address is no longer delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" # check records in db-sync tx_db_deleg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_deleg, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) src_address = delegation_out.pool_user.payment.address LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_id}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # files for deregistering / re-registering stake address stake_addr_dereg_cert_file = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_undeleg_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_undeleg_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) tx_files_undeleg = clusterlib.TxFiles( certificate_files=[ stake_addr_dereg_cert_file, stake_addr_reg_cert_file ], signing_key_files=[ delegation_out.pool_user.payment.skey_file, delegation_out.pool_user.stake.skey_file, ], ) src_payment_balance = cluster.get_address_balance(src_address) reward_balance = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance # withdraw rewards to payment address; deregister and re-register stake address tx_raw_undeleg = cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_undeleg_withdraw", tx_files=tx_files_undeleg, withdrawals=[ clusterlib.TxOut( address=delegation_out.pool_user.stake.address, amount=-1) ], ) # check that the key deposit was NOT returned and rewards were withdrawn assert ( cluster.get_address_balance(src_address) == src_payment_balance - tx_raw_undeleg.fee + reward_balance ), f"Incorrect balance for source address `{src_address}`" # check that the stake address is no longer delegated stake_addr_info = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address) assert stake_addr_info.address, f"Reward address is not registered: {stake_addr_info}" assert (not stake_addr_info.delegation ), f"Stake address is still delegated: {stake_addr_info}" this_epoch = cluster.wait_for_new_epoch(padding_seconds=20) assert cluster.get_stake_addr_info( delegation_out.pool_user.stake.address ).reward_account_balance, "No reward was received next epoch after undelegation" # check `transaction view` command tx_view.check_tx_view(cluster_obj=cluster, tx_raw_output=tx_raw_undeleg) # check records in db-sync tx_db_undeleg = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_undeleg) if tx_db_undeleg: assert delegation_out.pool_user.stake.address in tx_db_undeleg.stake_deregistration assert delegation_out.pool_user.stake.address in tx_db_undeleg.stake_registration db_rewards = dbsync_utils.check_address_reward( address=delegation_out.pool_user.stake.address, epoch_from=init_epoch) assert db_rewards db_reward_epochs = sorted(r.spendable_epoch for r in db_rewards.rewards) assert db_reward_epochs[0] == init_epoch + 4 assert this_epoch in db_reward_epochs
def test_lobster_name(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Test the Lobster Challenge. Uses `cardano-cli transaction build` command for building the transactions. * fund token issuer and create a UTxO for collateral * mint the LobsterNFT token * deploy the LobsterNFT token to address of lobster spending script * generate random votes and determine the expected final value * perform voting and check that the final value matches the expected value * (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] votes_num = 50 names_num = 1219 io_random_seed = 42 issuer_fund = 200_000_000 lovelace_setup_amount = 1_724_100 lovelace_vote_amount = 2_034_438 collateral_amount = 20_000_000 nft_amount = 1 # 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, amount=issuer_fund, collateral_amount=collateral_amount, ) # Step 2: mint the LobsterNFT token lobster_nft_token, token_utxos_step2, tx_output_step2 = _mint_lobster_nft( cluster_obj=cluster, temp_template=temp_template, issuer_addr=issuer_addr, mint_utxos=mint_utxos, collateral_utxos=collateral_utxos, nft_amount=nft_amount, lovelace_amount=lovelace_setup_amount, ) # Step 3: deploy the LobsterNFT token to script address script_address, token_utxos_step3, tx_output_step3 = _deploy_lobster_nft( cluster_obj=cluster, temp_template=temp_template, issuer_addr=issuer_addr, token_utxos=token_utxos_step2, lobster_nft_token=lobster_nft_token, nft_amount=nft_amount, lovelace_amount=lovelace_setup_amount, ) tx_outputs_all = [tx_output_step1, tx_output_step2, tx_output_step3] # Step 4: prepare for voting # there's 50 votes, each vote is int between 1 and 100 votes = [random.randint(1, 100) for __ in range(votes_num)] _votes_sum = sum(votes) # Add "random" seed to the sum of all votes. Taking the remainder after # division by the number of potential names (`names_num`) gives us the # final counter value. # The final counter value is used as an index. Looking into the list of # names, we can see the name the index points to. We don't need to do # that in automated test, we will just check that the final counter # value matches the expected counter value. expected_counter_val = (io_random_seed + _votes_sum) % names_num votes.append(expected_counter_val) # Step 5: vote other_policyid = cluster.get_policyid(OTHER_MINT_PLUTUS) asset_name_counter = b"LobsterCounter".hex() asset_name_votes = b"LobsterVotes".hex() counter_token = f"{other_policyid}.{asset_name_counter}" votes_token = f"{other_policyid}.{asset_name_votes}" vote_utxos = token_utxos_step3 vote_counter = 0 utxo_votes_token: Optional[clusterlib.UTXOData] = None utxo_counter_token: Optional[clusterlib.UTXOData] = None for vote_num, vote_val in enumerate(votes, start=1): # normal votes if vote_num <= votes_num: vote_counter += vote_val mint_val = vote_val # final IO vote else: # set new counter value to `(seed + counter value) % number of names` # and burn excesive LobsterCounter tokens mint_val = vote_val - vote_counter vote_counter = vote_val txouts = [ # Lovelace amount clusterlib.TxOut( address=script_address, amount=lovelace_vote_amount, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterNFT token clusterlib.TxOut( address=script_address, amount=nft_amount, coin=lobster_nft_token, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterCounter token clusterlib.TxOut( address=script_address, amount=vote_counter, coin=counter_token, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterVotes token clusterlib.TxOut( address=script_address, amount=vote_num, coin=votes_token, datum_hash=LOBSTER_DATUM_HASH, ), ] mint_txouts = [ # mint new LobsterCounter tokens clusterlib.TxOut( address=script_address, amount=mint_val, coin=counter_token, datum_hash=LOBSTER_DATUM_HASH, ), # mint 1 new LobsterVotes token clusterlib.TxOut( address=script_address, amount=1, coin=votes_token, datum_hash=LOBSTER_DATUM_HASH, ), ] mint_script_data = [ clusterlib.Mint( txouts=mint_txouts, script_file=OTHER_MINT_PLUTUS, redeemer_value="[]", ) ] txin_script_data = [ clusterlib.ScriptTxIn( txins=vote_utxos, script_file=LOBSTER_PLUTUS, collaterals=collateral_utxos, datum_value="[]", redeemer_value="[]", ) ] tx_files = clusterlib.TxFiles(signing_key_files=[ payment_addr.skey_file, issuer_addr.skey_file ], ) funds_txin = cluster.get_utxo_with_highest_amount( address=payment_addr.address) tx_output_vote = cluster.build_tx( src_address=payment_addr.address, tx_name=f"{temp_template}_voting_{vote_num}", txins=[funds_txin], tx_files=tx_files, txouts=txouts, script_txins=txin_script_data, mint=mint_script_data, ) tx_signed = cluster.sign_tx( tx_body_file=tx_output_vote.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_voting_{vote_num}", ) cluster.submit_tx(tx_file=tx_signed, txins=vote_utxos) tx_outputs_all.append(tx_output_vote) txid_vote = cluster.get_txid(tx_body_file=tx_output_vote.out_file) vote_utxos = cluster.get_utxo(txin=f"{txid_vote}#1") # check expected balances utxos_lovelace = [ u for u in vote_utxos if u.coin == clusterlib.DEFAULT_COIN ][0] assert ( utxos_lovelace.amount == lovelace_vote_amount ), f"Incorrect Lovelace balance for script address `{script_address}`" utxo_votes_token = [ u for u in vote_utxos if u.coin == votes_token ][0] assert ( utxo_votes_token.amount == vote_num ), f"Incorrect LobsterVotes token balance for script address `{script_address}`" utxo_counter_token = [ u for u in vote_utxos if u.coin == counter_token ][0] assert ( utxo_counter_token.amount == vote_counter ), f"Incorrect LobsterCounter token balance for script address `{script_address}`" assert ( utxo_counter_token and utxo_counter_token.amount == expected_counter_val ), "Final balance of LobsterCounter token doesn't match the expected balance" # check transactions in db-sync for tx_out_rec in tx_outputs_all: dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_out_rec)
def test_no_reward_unmet_pledge2( self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Check that the stake pool is not receiving rewards when pledge is not met. When the pledge is higher than available funds, neither pool owners nor those who delegate to that pool receive rewards. * delegate stake address * wait for first reward * withdraw part of the pledge - the funds are lower than what is needed by the stake pool * check that NO new rewards were received by those delegating to the pool * check that pool owner is also NOT receiving rewards * return the pool to the original state - restore pledge funds * check that new rewards were received by those delegating to the pool * check that pool owner is also receiving rewards """ pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_owner = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["stake"]) temp_template = common.get_test_id(cluster) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=pool_name) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to withdraw the pledge in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # load pool data loaded_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=f"changed_{pool_name}", pool_id=pool_id) pledge_amount = loaded_data.pool_pledge // 2 # withdraw part of the pledge destinations = [ clusterlib.TxOut(address=delegation_out.pool_user.payment.address, amount=pledge_amount) ] tx_files = clusterlib.TxFiles( signing_key_files=[pool_owner.payment.skey_file]) cluster.send_funds( src_address=pool_owner.payment.address, destinations=destinations, tx_name=f"{temp_template}_withdraw_pledge", tx_files=tx_files, ) assert cluster.get_address_balance( pool_owner.payment.address ) < loaded_data.pool_pledge, ( f"Pledge still high - pledge: {loaded_data.pool_pledge}, " f"funds: {cluster.get_address_balance(pool_owner.payment.address)}" ) cluster.wait_for_new_epoch(4, padding_seconds=30) orig_owner_reward = cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance orig_user_reward = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance cluster.wait_for_new_epoch(3) with cluster_manager.restart_on_failure(): # check that NO new rewards were received by those delegating to the pool assert (orig_user_reward == cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "Received unexpected rewards" # check that pool owner is also NOT receiving rewards assert (orig_owner_reward == cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "Pool owner received unexpected rewards" # Return the pool to the original state - restore pledge funds. # fund user address so it has enough funds for fees etc. clusterlib_utils.fund_from_faucet( delegation_out.pool_user, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) # return pledge destinations = [ clusterlib.TxOut(address=pool_owner.payment.address, amount=pledge_amount + 100_000_000) ] tx_files = clusterlib.TxFiles( signing_key_files=[delegation_out.pool_user.payment.skey_file]) cluster.send_funds( src_address=delegation_out.pool_user.payment.address, destinations=destinations, tx_name=f"{temp_template}_return_pledge", tx_files=tx_files, ) assert ( cluster.get_address_balance( pool_owner.payment.address) >= loaded_data.pool_pledge ), (f"Funds still low - pledge: {loaded_data.pool_pledge}, " f"funds: {cluster.get_address_balance(pool_owner.payment.address)}" ) cluster.wait_for_new_epoch(5, padding_seconds=30) # check that new rewards were received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # check that pool owner is also receiving rewards assert (orig_owner_reward < cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "New reward was not received by pool reward address"