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 tx_fee == expected_fee, "Expected fee doesn't match the actual fee"
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()}_{clusterlib_utils.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 deregister_stake_addr(cluster_obj: clusterlib.ClusterLib, pool_user: clusterlib.PoolUser, name_template: str) -> clusterlib.TxRawOutput: """Deregister stake address.""" # files for deregistering stake address stake_addr_dereg_cert = cluster_obj.gen_stake_addr_deregistration_cert( addr_name=f"{name_template}_addr0_dereg", stake_vkey_file=pool_user.stake.vkey_file) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ pool_user.payment.skey_file, pool_user.stake.skey_file ], ) # withdraw rewards to payment address withdraw_reward(cluster_obj=cluster_obj, pool_user=pool_user, name_template=name_template) tx_raw_output = cluster_obj.send_tx( src_address=pool_user.payment.address, tx_name=f"{name_template}_dereg_stake_addr", tx_files=tx_files_deregister, ) cluster_obj.wait_for_new_block(new_blocks=2) return tx_raw_output
def _create_pool_certificates( self, cluster_obj: clusterlib.ClusterLib, pool_owners: List[clusterlib.PoolUser], temp_template: str, pool_data: clusterlib.PoolData, ) -> Tuple[str, clusterlib.TxFiles]: """Create certificates for registering a stake pool, delegating stake address.""" # create node VRF key pair node_vrf = cluster_obj.gen_vrf_key_pair(node_name=pool_data.pool_name) # create node cold key pair and counter node_cold = cluster_obj.gen_cold_key_pair_and_counter( node_name=pool_data.pool_name) # create stake address registration certs stake_addr_reg_cert_files = [ cluster_obj.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file) for i, p in enumerate(pool_owners) ] # create stake address delegation cert stake_addr_deleg_cert_files = [ cluster_obj.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file, cold_vkey_file=node_cold.vkey_file, ) for i, p in enumerate(pool_owners) ] # create stake pool registration cert pool_reg_cert_file = cluster_obj.gen_pool_registration_cert( pool_data=pool_data, vrf_vkey_file=node_vrf.vkey_file, cold_vkey_file=node_cold.vkey_file, owner_stake_vkey_files=[p.stake.vkey_file for p in pool_owners], ) src_address = pool_owners[0].payment.address # register and delegate stake address, create and register pool tx_files = clusterlib.TxFiles( certificate_files=[ pool_reg_cert_file, *stake_addr_reg_cert_files, *stake_addr_deleg_cert_files, ], signing_key_files=[ *[p.payment.skey_file for p in pool_owners], *[p.stake.skey_file for p in pool_owners], node_cold.skey_file, ], ) return src_address, tx_files
def test_normal_tx_from_script_addr( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Try to send funds from script address using TX signed with skeys. Sending funds from script address is expected to fail when not using witness files. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ANY, payment_vkey_files=payment_vkey_files, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=payment_addrs[0].address, dst_address=script_addr, amount=300_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address destinations_from = [ clusterlib.TxOut(address=payment_addrs[0].address, amount=1000) ] tx_files_from = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) # cannot send the TX without signing it using witness files with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=script_addr, tx_name=temp_template, destinations=destinations_from, tx_files=tx_files_from, ) assert "MissingScriptWitnessesUTXOW" in str(excinfo.value)
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_addr = 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_addr) # send funds to script address destinations = [clusterlib.TxOut(address=script_addr, 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_addr) == dst_init_balance + amount ), f"Incorrect balance for destination address `{script_addr}`"
def test_tx_script_invalid(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Build transaction with invalid auxiliary script. Expect failure. """ temp_template = helpers.get_func_name() tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], script_files=[self.JSON_METADATA_FILE], # not valid script file ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx(src_address=payment_addrs[0].address, tx_name=temp_template, tx_files=tx_files) assert 'Error in $: key "type" not found' in str(excinfo.value)
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"{helpers.get_func_name()}_{fee_add}" amount = 100 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, ) cluster.wait_for_new_block(new_blocks=2) 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 withdraw_reward( cluster_obj: clusterlib.ClusterLib, pool_user: clusterlib.PoolUser, name_template: str, dst_addr_record: Optional[clusterlib.AddressRecord] = None, ) -> None: """Withdraw rewards to payment address.""" dst_addr_record = dst_addr_record or pool_user.payment dst_address = dst_addr_record.address src_init_balance = cluster_obj.get_address_balance(dst_address) tx_files_withdrawal = clusterlib.TxFiles(signing_key_files=[ dst_addr_record.skey_file, pool_user.stake.skey_file ], ) this_epoch = cluster_obj.get_last_block_epoch() tx_raw_withdrawal_output = cluster_obj.send_tx( src_address=dst_address, tx_name=f"{name_template}_reward_withdrawal", tx_files=tx_files_withdrawal, withdrawals=[ clusterlib.TxOut(address=pool_user.stake.address, amount=-1) ], ) cluster_obj.wait_for_new_block(new_blocks=2) if this_epoch != cluster_obj.get_last_block_epoch(): LOGGER.warning( "New epoch during rewards withdrawal! Reward account may not be empty." ) else: # check that reward is 0 assert (cluster_obj.get_stake_addr_info( pool_user.stake.address).reward_account_balance == 0 ), "Not all rewards were transfered" # check that rewards were transfered src_reward_balance = cluster_obj.get_address_balance(dst_address) assert (src_reward_balance == src_init_balance - tx_raw_withdrawal_output.fee + tx_raw_withdrawal_output.withdrawals[0].amount # type: ignore ), f"Incorrect balance for destination address `{dst_address}`"
def test_tx_script_metadata_cbor( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send transaction with auxiliary script and metadata CBOR. Check that the auxiliary script is present in the TX body. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.AT_LEAST, payment_vkey_files=payment_vkey_files, required=2, slot=1000, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], metadata_cbor_files=[self.CBOR_METADATA_FILE], script_files=[multisig_script], ) tx_raw_output = cluster.send_tx(src_address=payment_addrs[0].address, tx_name=temp_template, tx_files=tx_files) cluster.wait_for_new_block(new_blocks=2) assert tx_raw_output.fee, "Transaction had no fee" with open(tx_raw_output.out_file) as body_fp: tx_body_json = json.load(body_fp) cbor_body = bytes.fromhex(tx_body_json["cborHex"]) cbor_body_metadata = cbor2.loads(cbor_body)[1] cbor_body_script = cbor_body_metadata[1] assert cbor_body_script, "Auxiliary script not present"
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(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 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 get_timestamped_rand_str() tx_name = f"{tx_name}_return_funds" with helpers.FileLockIfXdist( f"{helpers.TEST_TEMP_DIR}/{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, ) cluster_obj.wait_for_new_block(new_blocks=2) except Exception: pass finally: logging.disable(logging.NOTSET)
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"{helpers.get_func_name()}_{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 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_last_block_epoch() + 1, ) # submit the pool deregistration certificate through a tx 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 tx_fee == expected_fee, "Expected fee doesn't match the actual fee"
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.TEST_TEMP_DIR}/{cluster_obj.genesis_utxo_addr}.lock"): tx_name = tx_name or get_timestamped_rand_str() tx_name = f"{tx_name}_genesis_funding" fund_tx_files = clusterlib.TxFiles(signing_key_files=[ *cluster_obj.delegate_skeys, cluster_obj.genesis_utxo_skey ]) cluster_obj.send_funds( src_address=cluster_obj.genesis_utxo_addr, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, destination_dir=destination_dir, ) cluster_obj.wait_for_new_block(new_blocks=2) def return_funds_to_faucet( *src_addrs: clusterlib.AddressRecord, cluster_obj: clusterlib.ClusterLib,