def check_authorities_and_deposit( self, token_dict: Dict[bytes, TokenInfo]) -> None: """Verify that the sum of outputs is equal of the sum of inputs, for each token. If sum of inputs and outputs is not 0, make sure inputs have mint/melt authority. token_dict sums up all tokens present in the tx and their properties (amount, can_mint, can_melt) amount = outputs - inputs, thus: - amount < 0 when melting - amount > 0 when minting :raises InputOutputMismatch: if sum of inputs is not equal to outputs and there's no mint/melt """ withdraw = 0 deposit = 0 for token_uid, token_info in token_dict.items(): if token_uid == settings.HATHOR_TOKEN_UID: continue if token_info.amount == 0: # that's the usual behavior, nothing to do pass elif token_info.amount < 0: # tokens have been melted if not token_info.can_melt: raise InputOutputMismatch( '{} {} tokens melted, but there is no melt authority input' .format(token_info.amount, token_uid.hex())) withdraw += get_withdraw_amount(token_info.amount) else: # tokens have been minted if not token_info.can_mint: raise InputOutputMismatch( '{} {} tokens minted, but there is no mint authority input' .format((-1) * token_info.amount, token_uid.hex())) deposit += get_deposit_amount(token_info.amount) # check whether the deposit/withdraw amount is correct htr_expected_amount = withdraw - deposit htr_info = token_dict[settings.HATHOR_TOKEN_UID] if htr_info.amount != htr_expected_amount: raise InputOutputMismatch( 'HTR balance is different than expected. (amount={}, expected={})' .format( htr_info.amount, htr_expected_amount, ))
def create_tokens(manager: 'HathorManager', address_b58: str = None, mint_amount: int = 300, token_name: str = 'TestCoin', token_symbol: str = 'TTC', propagate: bool = True): """Creates a new token and propagates a tx with the following UTXOs: 0. some tokens (already mint some tokens so they can be transferred); 1. mint authority; 2. melt authority; 3. deposit change; :param manager: hathor manager :type manager: :class:`hathor.manager.HathorManager` :param address_b58: address where tokens will be transferred to :type address_b58: string :param token_name: the token name for the new token :type token_name: str :param token_symbol: the token symbol for the new token :type token_symbol: str :return: the propagated transaction so others can spend their outputs """ genesis = manager.tx_storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_txs = [tx for tx in genesis if not tx.is_block] genesis_block = genesis_blocks[0] genesis_private_key = get_genesis_key() wallet = manager.wallet outputs = [] if address_b58 is None: address_b58 = wallet.get_unused_address(mark_as_used=True) address = decode_address(address_b58) parents = [tx.hash for tx in genesis_txs] script = P2PKH.create_output_script(address) # deposit input deposit_amount = get_deposit_amount(mint_amount) deposit_input = TxInput(genesis_block.hash, 0, b'') # mint output if mint_amount > 0: outputs.append(TxOutput(mint_amount, script, 0b00000001)) # authority outputs outputs.append(TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)) outputs.append(TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001)) # deposit output outputs.append(TxOutput(genesis_block.outputs[0].value - deposit_amount, script, 0)) tx = TokenCreationTransaction( weight=1, parents=parents, storage=manager.tx_storage, inputs=[deposit_input], outputs=outputs, token_name=token_name, token_symbol=token_symbol, timestamp=int(manager.reactor.seconds()) ) data_to_sign = tx.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() if propagate: tx.verify() manager.propagate_tx(tx, fails_silently=False) manager.reactor.advance(8) return tx
def test_token_index_with_conflict(self, mint_amount=0): # create a new token and have a mint operation done. The tx that mints the # tokens has the following outputs: # 0. minted tokens # 1. mint authority; # 2. melt authority # 3. HTR deposit change tx = create_tokens(self.manager, self.address_b58, mint_amount=100) token_uid = tx.tokens[0] tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info( tx.tokens[0]) mint = list(tokens_index.iter_mint_utxos()) melt = list(tokens_index.iter_melt_utxos()) self.assertIn(TokenUtxoInfo(tx.hash, 1), mint) self.assertIn(TokenUtxoInfo(tx.hash, 2), melt) # there should only be one element on the indexes for the token self.assertEqual(1, len(mint)) self.assertEqual(1, len(melt)) # check total amount of tokens self.assertEqual(100, tokens_index.get_total()) # new tx minting tokens mint_amount = 300 deposit_amount = get_deposit_amount(mint_amount) script = P2PKH.create_output_script(self.address) # inputs mint_input = TxInput(tx.hash, 1, b'') melt_input = TxInput(tx.hash, 2, b'') deposit_input = TxInput(tx.hash, 3, b'') # outputs mint_output = TxOutput(mint_amount, script, 1) authority_output1 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001) authority_output2 = TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001) deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script, 0) tx2 = Transaction(weight=1, inputs=[mint_input, melt_input, deposit_input], outputs=[ authority_output1, authority_output2, mint_output, deposit_output ], parents=self.manager.get_new_tx_parents(), tokens=[token_uid], storage=self.manager.tx_storage, timestamp=int(self.clock.seconds())) # sign inputs wallet = self.manager.wallet data_to_sign = tx2.get_sighash_all() public_bytes, signature = wallet.get_input_aux_data( data_to_sign, wallet.get_private_key(self.address_b58)) data = P2PKH.create_input_data(public_bytes, signature) tx2.inputs[0].data = data tx2.inputs[1].data = data tx2.inputs[2].data = data tx2.resolve() tx2.verify() self.manager.propagate_tx(tx2) self.run_to_completion() # there should only be one element on the indexes for the token tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info( tx.tokens[0]) mint = list(tokens_index.iter_mint_utxos()) melt = list(tokens_index.iter_melt_utxos()) self.assertEqual(1, len(mint)) self.assertEqual(1, len(melt)) self.assertIn(TokenUtxoInfo(tx2.hash, 0), mint) self.assertIn(TokenUtxoInfo(tx2.hash, 1), melt) # check total amount of tokens has been updated self.assertEqual(400, tokens_index.get_total()) # create conflicting tx by changing parents tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.parents = [tx.parents[1], tx.parents[0]] tx3.weight = 3 tx3.resolve() self.assertNotEqual(tx3.hash, tx2.hash) self.assertTrue(tx3.weight > tx2.weight) self.manager.propagate_tx(tx3) self.run_to_completion() # new tx should be on tokens index. Old tx should not be present tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info( tx.tokens[0]) mint = list(tokens_index.iter_mint_utxos()) melt = list(tokens_index.iter_melt_utxos()) self.assertIn(TokenUtxoInfo(tx3.hash, 0), mint) self.assertIn(TokenUtxoInfo(tx3.hash, 1), melt) # there should only be one element on the indexes for the token self.assertEqual(1, len(mint)) self.assertEqual(1, len(melt)) # should have same amount of tokens self.assertEqual(400, tokens_index.get_total())
def create_tokens(manager: 'HathorManager', address_b58: Optional[str] = None, mint_amount: int = 300, token_name: str = 'TestCoin', token_symbol: str = 'TTC', propagate: bool = True, use_genesis: bool = True, nft_data: Optional[str] = None) -> TokenCreationTransaction: """Creates a new token and propagates a tx with the following UTXOs: 0. some tokens (already mint some tokens so they can be transferred); 1. mint authority; 2. melt authority; 3. deposit change; :param manager: hathor manager :type manager: :class:`hathor.manager.HathorManager` :param address_b58: address where tokens will be transferred to :type address_b58: string :param token_name: the token name for the new token :type token_name: str :param token_symbol: the token symbol for the new token :type token_symbol: str :param use_genesis: If True will use genesis outputs to create token, otherwise will use manager wallet :type token_symbol: bool :param nft_data: If not None we create a first output as the NFT data script :type nft_data: str :return: the propagated transaction so others can spend their outputs """ wallet = manager.wallet assert wallet is not None if address_b58 is None: address_b58 = wallet.get_unused_address(mark_as_used=True) address = decode_address(address_b58) script = P2PKH.create_output_script(address) deposit_amount = get_deposit_amount(mint_amount) if nft_data: # NFT creation needs 0.01 HTR of fee deposit_amount += 1 genesis = manager.tx_storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_txs = [tx for tx in genesis if not tx.is_block] genesis_block = genesis_blocks[0] genesis_private_key = get_genesis_key() change_output: Optional[TxOutput] parents: List[bytes] if use_genesis: genesis_hash = genesis_block.hash assert genesis_hash is not None deposit_input = [TxInput(genesis_hash, 0, b'')] change_output = TxOutput(genesis_block.outputs[0].value - deposit_amount, script, 0) parents = [cast(bytes, tx.hash) for tx in genesis_txs] timestamp = int(manager.reactor.seconds()) else: total_reward = 0 deposit_input = [] while total_reward < deposit_amount: block = add_new_block(manager, advance_clock=1, address=address) deposit_input.append(TxInput(block.hash, 0, b'')) total_reward += block.outputs[0].value if total_reward > deposit_amount: change_output = TxOutput(total_reward - deposit_amount, script, 0) else: change_output = None add_blocks_unlock_reward(manager) timestamp = int(manager.reactor.seconds()) parents = manager.get_new_tx_parents(timestamp) outputs = [] if nft_data: script_data = DataScript.create_output_script(nft_data) output_data = TxOutput(1, script_data, 0) outputs.append(output_data) # mint output if mint_amount > 0: outputs.append(TxOutput(mint_amount, script, 0b00000001)) # authority outputs outputs.append(TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)) outputs.append(TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001)) # deposit output if change_output: outputs.append(change_output) tx = TokenCreationTransaction( weight=1, parents=parents, storage=manager.tx_storage, inputs=deposit_input, outputs=outputs, token_name=token_name, token_symbol=token_symbol, timestamp=timestamp ) data_to_sign = tx.get_sighash_all() if use_genesis: public_bytes, signature = wallet.get_input_aux_data(data_to_sign, genesis_private_key) else: private_key = wallet.get_private_key(address_b58) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, private_key) for input_ in tx.inputs: input_.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() if propagate: tx.verify() manager.propagate_tx(tx, fails_silently=False) manager.reactor.advance(8) return tx
def test_token_mint(self): wallet = self.manager.wallet tx = create_tokens(self.manager, self.address_b58, mint_amount=500) token_uid = tx.tokens[0] parents = self.manager.get_new_tx_parents() script = P2PKH.create_output_script(self.address) # mint tokens and transfer mint authority mint_amount = 10000000 deposit_amount = get_deposit_amount(mint_amount) _input1 = TxInput(tx.hash, 1, b'') _input2 = TxInput(tx.hash, 3, b'') token_output1 = TxOutput(mint_amount, script, 1) token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001) deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script, 0) tx2 = Transaction( weight=1, inputs=[_input1, _input2], outputs=[token_output1, token_output2, deposit_output], parents=parents, tokens=[token_uid], storage=self.manager.tx_storage, timestamp=int(self.clock.seconds())) data_to_sign = tx2.get_sighash_all() public_bytes, signature = wallet.get_input_aux_data( data_to_sign, wallet.get_private_key(self.address_b58)) data = P2PKH.create_input_data(public_bytes, signature) tx2.inputs[0].data = data tx2.inputs[1].data = data tx2.resolve() tx2.verify() self.manager.propagate_tx(tx2) self.run_to_completion() # check tokens index tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info( token_uid) mint = list(tokens_index.iter_mint_utxos()) melt = list(tokens_index.iter_melt_utxos()) self.assertIn(TokenUtxoInfo(tx2.hash, 1), mint) self.assertIn(TokenUtxoInfo(tx.hash, 2), melt) # there should only be one element on the indexes for the token self.assertEqual(1, len(mint)) self.assertEqual(1, len(melt)) # check total amount of tokens self.assertEqual(500 + mint_amount, tokens_index.get_total()) # try to mint 1 token unit without deposit mint_amount = 1 _input1 = TxInput(tx.hash, 1, b'') token_output1 = TxOutput(mint_amount, script, 1) token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001) tx3 = Transaction(weight=1, inputs=[_input1], outputs=[token_output1, token_output2], parents=parents, tokens=[token_uid], storage=self.manager.tx_storage, timestamp=int(self.clock.seconds())) data_to_sign = tx3.get_sighash_all() public_bytes, signature = wallet.get_input_aux_data( data_to_sign, wallet.get_private_key(self.address_b58)) data = P2PKH.create_input_data(public_bytes, signature) tx3.inputs[0].data = data tx3.resolve() with self.assertRaises(InputOutputMismatch): tx3.verify() # try to mint and deposit less tokens than necessary mint_amount = 10000000 deposit_amount = get_deposit_amount(mint_amount) - 1 _input1 = TxInput(tx.hash, 1, b'') _input2 = TxInput(tx.hash, 3, b'') token_output1 = TxOutput(mint_amount, script, 1) token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001) deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script, 0) tx4 = Transaction( weight=1, inputs=[_input1, _input2], outputs=[token_output1, token_output2, deposit_output], parents=parents, tokens=[token_uid], storage=self.manager.tx_storage, timestamp=int(self.clock.seconds())) data_to_sign = tx4.get_sighash_all() public_bytes, signature = wallet.get_input_aux_data( data_to_sign, wallet.get_private_key(self.address_b58)) data = P2PKH.create_input_data(public_bytes, signature) tx4.inputs[0].data = data tx4.inputs[1].data = data tx4.resolve() with self.assertRaises(InputOutputMismatch): tx4.verify() # try to mint using melt authority UTXO _input1 = TxInput(tx.hash, 2, b'') token_output = TxOutput(10000000, script, 1) tx5 = Transaction(weight=1, inputs=[_input1], outputs=[token_output], parents=parents, tokens=[token_uid], storage=self.manager.tx_storage, timestamp=int(self.clock.seconds())) data_to_sign = tx5.get_sighash_all() public_bytes, signature = wallet.get_input_aux_data( data_to_sign, wallet.get_private_key(self.address_b58)) tx5.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx5.resolve() with self.assertRaises(InputOutputMismatch): tx5.verify()