# Copyright © 2020 Interplanetary Database Association e.V., # Planetmint and IPDB software contributors. # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 # # Divisible assets integration testing # This test checks if we can successfully divide assets. # The script tests various things like: # # - create a transaction with a divisible asset and issue them to someone # - check if the transaction is stored and has the right amount of tokens # - spend some tokens # - try to spend more tokens than available # # We run a series of checks for each step, that is retrieving # the transaction from the remote system, and also checking the `amount` # of a given transaction. # ## Imports # We need the `pytest` package to catch the `BadRequest` exception properly. # And of course, we also need the `BadRequest`. import pytest from planetmint_driver.exceptions import BadRequest # Import generate_keypair to create actors from planetmint_driver.crypto import generate_keypair # import helper to manage multiple nodes from .helper.hosts import Hosts def test_divisible_assets(): # ## Set up a connection to Planetmint # Check [test_basic.py](./test_basic.html) to get some more details # about the endpoint. hosts = Hosts("/shared/hostnames") pm = hosts.get_connection() # Oh look, it is Alice again and she brought her friend Bob along. alice, bob = generate_keypair(), generate_keypair() # ## Alice creates a time sharing token # Alice wants to go on vacation, while Bobs bike just broke down. # Alice decides to rent her bike to Bob while she is gone. # So she prepares a `CREATE` transaction to issues 10 tokens. # First, she prepares an asset for a time sharing token. As you can see in # the description, Bob and Alice agree that each token can be used to ride # the bike for one hour. bike_token = { "data": { "token_for": {"bike": {"serial_number": 420420}}, "description": "Time share token. Each token equals one hour of riding.", }, } # She prepares a `CREATE` transaction and issues 10 tokens. # Here, Alice defines in a tuple that she wants to assign # these 10 tokens to Bob. prepared_token_tx = pm.transactions.prepare( operation="CREATE", signers=alice.public_key, recipients=[([bob.public_key], 10)], asset=bike_token ) # She fulfills and sends the transaction. fulfilled_token_tx = pm.transactions.fulfill(prepared_token_tx, private_keys=alice.private_key) pm.transactions.send_commit(fulfilled_token_tx) # We store the `id` of the transaction to use it later on. bike_token_id = fulfilled_token_tx["id"] # Let's check if the transaction was successful. assert pm.transactions.retrieve(bike_token_id), "Cannot find transaction {}".format(bike_token_id) # Bob owns 10 tokens now. assert pm.transactions.retrieve(bike_token_id)["outputs"][0]["amount"] == "10" # ## Bob wants to use the bike # Now that Bob got the tokens and the sun is shining, he wants to get out # with the bike for three hours. # To use the bike he has to send the tokens back to Alice. # To learn about the details of transferring a transaction check out # [test_basic.py](./test_basic.html) transfer_asset = {"id": bike_token_id} output_index = 0 output = fulfilled_token_tx["outputs"][output_index] transfer_input = { "fulfillment": output["condition"]["details"], "fulfills": {"output_index": output_index, "transaction_id": fulfilled_token_tx["id"]}, "owners_before": output["public_keys"], } # To use the tokens Bob has to reassign 7 tokens to himself and the # amount he wants to use to Alice. prepared_transfer_tx = pm.transactions.prepare( operation="TRANSFER", asset=transfer_asset, inputs=transfer_input, recipients=[([alice.public_key], 3), ([bob.public_key], 7)], ) # He signs and sends the transaction. fulfilled_transfer_tx = pm.transactions.fulfill(prepared_transfer_tx, private_keys=bob.private_key) sent_transfer_tx = pm.transactions.send_commit(fulfilled_transfer_tx) # First, Bob checks if the transaction was successful. assert pm.transactions.retrieve(fulfilled_transfer_tx["id"]) == sent_transfer_tx hosts.assert_transaction(fulfilled_transfer_tx["id"]) # There are two outputs in the transaction now. # The first output shows that Alice got back 3 tokens... assert pm.transactions.retrieve(fulfilled_transfer_tx["id"])["outputs"][0]["amount"] == "3" # ... while Bob still has 7 left. assert pm.transactions.retrieve(fulfilled_transfer_tx["id"])["outputs"][1]["amount"] == "7" # ## Bob wants to ride the bike again # It's been a week and Bob wants to right the bike again. # Now he wants to ride for 8 hours, that's a lot Bob! # He prepares the transaction again. transfer_asset = {"id": bike_token_id} # This time we need an `output_index` of 1, since we have two outputs # in the `fulfilled_transfer_tx` we created before. The first output with # index 0 is for Alice and the second output is for Bob. # Since Bob wants to spend more of his tokens he has to provide the # correct output with the correct amount of tokens. output_index = 1 output = fulfilled_transfer_tx["outputs"][output_index] transfer_input = { "fulfillment": output["condition"]["details"], "fulfills": {"output_index": output_index, "transaction_id": fulfilled_transfer_tx["id"]}, "owners_before": output["public_keys"], } # This time Bob only provides Alice in the `recipients` because he wants # to spend all his tokens prepared_transfer_tx = pm.transactions.prepare( operation="TRANSFER", asset=transfer_asset, inputs=transfer_input, recipients=[([alice.public_key], 8)] ) fulfilled_transfer_tx = pm.transactions.fulfill(prepared_transfer_tx, private_keys=bob.private_key) # Oh Bob, what have you done?! You tried to spend more tokens than you had. # Remember Bob, last time you spent 3 tokens already, # so you only have 7 left. with pytest.raises(BadRequest) as error: pm.transactions.send_commit(fulfilled_transfer_tx) # Now Bob gets an error saying that the amount he wanted to spent is # higher than the amount of tokens he has left. assert error.value.args[0] == 400 message = ( "Invalid transaction (AmountError): The amount used in the " "inputs `7` needs to be same as the amount used in the " "outputs `8`" ) assert error.value.args[2]["message"] == message # We have to stop this test now, I am sorry, but Bob is pretty upset # about his mistake. See you next time :)