diff --git a/bigchaindb/assets.py b/bigchaindb/assets.py index 1eaa1fbb..c0cd1801 100644 --- a/bigchaindb/assets.py +++ b/bigchaindb/assets.py @@ -1,4 +1,4 @@ -from bigchaindb.exceptions import AssetIdMismatch, TransactionDoesNotExist +from bigchaindb.exceptions import AssetIdMismatch, TransactionDoesNotExist, AmountError def get_asset_id(txids, bigchain): @@ -34,3 +34,35 @@ def get_asset_id(txids, bigchain): if len(asset_ids) > 1: raise AssetIdMismatch("All inputs of a transaction need to have the same asset id.") return asset_ids.pop() + + +def validate_asset_creation(asset_data, divisible, updatable, refillable, amount): + """Validate digital asset + + Args: + asset_data (Optional[dict]): dictionary describing the digital asset (only used on a create transaction) + divisible (Optional[boolean): Whether the asset is divisible or not. Defaults to `False`. + updatable (Optional[boolean]): Whether the data in the asset can be updated in the future or not. + Defaults to `False`. + refillable (Optional[boolean]): Whether the amount of the asset can change after its creation. + Defaults to `False`. + amount (Optional[int]): The amount of "shares". Only relevant if the asset is marked as divisible. + Defaults to `1`. + """ + if asset_data is not None and not isinstance(asset_data, dict): + raise TypeError('`data` must be a dict instance or None') + if not isinstance(divisible, bool): + raise TypeError('`divisible` must be a boolean') + if not isinstance(refillable, bool): + raise TypeError('`refillable` must be a boolean') + if not isinstance(updatable, bool): + raise TypeError('`updatable` must be a boolean') + if not isinstance(amount, int): + raise TypeError('`amount` must be an int') + if divisible is False and amount != 1: + raise AmountError('Non-divisible assets must have amount 1') + if amount < 1: + raise AmountError('The amount cannot be less then 1') + + if divisible or updatable or refillable or amount != 1: + raise NotImplementedError("Divisible assets are not yet implemented!") diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 6191d794..bee4c6bb 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -3,12 +3,12 @@ from ..db.conftest import inputs def test_asset_creation(b, user_vk): - data = {'msg': 'hello'} - tx = b.create_transaction(b.me, user_vk, None, 'CREATE', data=data) + asset_data = {'msg': 'hello'} + tx = b.create_transaction(b.me, user_vk, None, 'CREATE', asset_data=asset_data) tx_signed = b.sign_transaction(tx, b.me_private) assert b.validate_transaction(tx_signed) == tx_signed - assert tx_signed['transaction']['asset']['data'] == data + assert tx_signed['transaction']['asset']['data'] == asset_data assert tx_signed['transaction']['asset']['refillable'] is False assert tx_signed['transaction']['asset']['divisible'] is False assert tx_signed['transaction']['asset']['updatable'] is False @@ -26,6 +26,75 @@ def test_asset_transfer(b, user_vk, user_sk): assert tx_transfer_signed['transaction']['asset'] == tx_create['transaction']['asset']['id'] +def test_validate_bad_asset_creation(b, user_vk): + from bigchaindb.util import get_hash_data + from bigchaindb.exceptions import AmountError + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['asset'].update({'divisible': 1}) + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(TypeError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['asset'].update({'refillable': 1}) + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(TypeError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['asset'].update({'updatable': 1}) + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(TypeError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['asset'].update({'data': 'a'}) + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(TypeError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['conditions'][0]['amount'] = 'a' + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(TypeError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['conditions'][0]['amount'] = 2 + tx['transaction']['asset'].update({'divisible': False}) + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(AmountError): + b.validate_transaction(tx_signed) + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['conditions'][0]['amount'] = 0 + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, b.me_private) + with pytest.raises(AmountError): + b.validate_transaction(tx_signed) + + +@pytest.mark.usefixtures('inputs') +def test_validate_bad_asset_transfer(b, user_vk, user_sk): + from bigchaindb.util import get_hash_data + from bigchaindb.exceptions import AssetIdMismatch + + tx_input = b.get_owned_ids(user_vk).pop() + tx = b.create_transaction(user_vk, user_vk, tx_input, 'TRANFER') + tx['transaction']['asset'] = 'aaa' + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, user_sk) + with pytest.raises(AssetIdMismatch): + b.validate_transaction(tx_signed) + + def test_validate_asset_arguments(b): from bigchaindb.exceptions import AmountError @@ -35,6 +104,8 @@ def test_validate_asset_arguments(b): b.create_transaction(b.me, b.me, None, 'CREATE', refillable=1) with pytest.raises(TypeError): b.create_transaction(b.me, b.me, None, 'CREATE', updatable=1) + with pytest.raises(TypeError): + b.create_transaction(b.me, b.me, None, 'CREATE', data='a') with pytest.raises(TypeError): b.create_transaction(b.me, b.me, None, 'CREATE', amount='a') with pytest.raises(AmountError): @@ -82,3 +153,10 @@ def test_asset_id_mismatch(b, user_vk): with pytest.raises(AssetIdMismatch): get_asset_id([tx_input1['txid'], tx_input2['txid']], bigchain=b) + + +def test_get_asset_id_transaction_does_not_exist(b, user_vk): + from bigchaindb.exceptions import TransactionDoesNotExist + + with pytest.raises(TransactionDoesNotExist): + b.create_transaction(user_vk, user_vk, {'txid': 'bored', 'cid': '0'}, 'TRANSFER') diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index cd38bcce..6874f3db 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -720,8 +720,9 @@ class TestMultipleInputs(object): assert len(tx.fulfillments) == 1 assert len(tx.conditions) == 1 - @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the same asset. Remove this after ', - 'implementing multiple assets')) + @pytest.mark.skipif(reason=('Multiple inputs are only allowed for the ' + 'same asset. Remove this after implementing ', + 'multiple assets')) def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk): from bigchaindb_common import crypto from bigchaindb.models import Transaction