diff --git a/src/apps/nem/helpers.py b/src/apps/nem/helpers.py index c7f9f65a1..46f7402df 100644 --- a/src/apps/nem/helpers.py +++ b/src/apps/nem/helpers.py @@ -21,6 +21,7 @@ NEM_SALT_SIZE = const(32) AES_BLOCK_SIZE = const(16) NEM_HASH_ALG = 'keccak' NEM_PUBLIC_KEY_SIZE = const(32) # ed25519 public key +NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE = const(10000) NEM_MAX_PLAIN_PAYLOAD_SIZE = const(1024) NEM_MAX_ENCRYPTED_PAYLOAD_SIZE = const(960) diff --git a/src/apps/nem/mosaic/definitions.py b/src/apps/nem/mosaic/definitions.py new file mode 100644 index 000000000..f283f074c --- /dev/null +++ b/src/apps/nem/mosaic/definitions.py @@ -0,0 +1,67 @@ +# todo move to common and generate via script + +def get_mosaic_definition(namespace_name: str, mosaic_name: str, network: int): + for m in mosaics: + if namespace_name == m["namespace"] and mosaic_name == m["mosaic"]: + if ("networks" not in m) or (network in m["networks"]): + return m + return None + + +mosaics = [ + { + "name": "XEM", + "ticker": " XEM", + "namespace": "nem", + "mosaic": "xem", + "divisibility": 6 + }, + { + "name": "DIMCOIN", + "ticker": " DIM", + "namespace": "dim", + "mosaic": "coin", + "divisibility": 6, + "levy": "MosaicLevy_Percentile", + "fee": 10, + "levy_namespace": "dim", + "levy_mosaic": "coin", + "networks": [ 104 ] + }, + { + "name": "DIM TOKEN", + "ticker": " DIMTOK", + "namespace": "dim", + "mosaic": "token", + "divisibility": 6, + "networks": [ 104 ] + }, + { + "name": "Breeze Token", + "ticker": " BREEZE", + "namespace": "breeze", + "mosaic": "breeze-token", + "divisibility": 0, + "networks": [ 104 ] + }, + { + "name": "PacNEM Game Credits", + "ticker": " PAC:HRT", + "namespace": "pacnem", + "mosaic": "heart", + "divisibility": 0, + "networks": [ 104 ] + }, + { + "name": "PacNEM Score Tokens", + "ticker": " PAC:CHS", + "namespace": "pacnem", + "mosaic": "cheese", + "divisibility": 6, + "levy": "MosaicLevy_Percentile", + "fee": 100, + "levy_namespace": "nem", + "levy_mosaic": "xem", + "networks": [ 104 ] + } +] diff --git a/src/apps/nem/transfer/layout.py b/src/apps/nem/transfer/layout.py index e20007d35..f3121645d 100644 --- a/src/apps/nem/transfer/layout.py +++ b/src/apps/nem/transfer/layout.py @@ -3,6 +3,9 @@ from trezor.messages import NEMImportanceTransferMode from trezor.messages import NEMTransfer from trezor.messages import NEMImportanceTransfer from trezor.messages import NEMTransactionCommon +from trezor.messages import NEMMosaic +from trezor.messages import NEMMosaicLevy +from apps.nem.mosaic.definitions import get_mosaic_definition async def ask_transfer(ctx, common: NEMTransactionCommon, transfer: NEMTransfer, payload, encrypted): @@ -10,13 +13,59 @@ async def ask_transfer(ctx, common: NEMTransactionCommon, transfer: NEMTransfer, await _require_confirm_payload(ctx, transfer.payload, encrypted) for mosaic in transfer.mosaics: - await require_confirm_content(ctx, 'Confirm mosaic', _mosaics_message(mosaic)) + await ask_transfer_mosaic(ctx, mosaic, common.network) await _require_confirm_transfer(ctx, transfer.recipient, transfer.amount) await require_confirm_final(ctx, common.fee) +async def ask_transfer_mosaic(ctx, mosaic: NEMMosaic, network: int): + definition = get_mosaic_definition(mosaic.namespace, mosaic.mosaic, network) + + if definition: + msg = Text('Confirm mosaic', ui.ICON_SEND, + ui.NORMAL, 'Confirm transfer of', + ui.BOLD, format_amount(mosaic.quantity, definition["divisibility"]) + definition["ticker"], + ui.NORMAL, 'of', + ui.BOLD, definition["name"], + icon_color=ui.GREEN) + await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput) + + if "levy" in definition and "fee" in definition: + levy_msg = _get_levy_msg(definition, mosaic.quantity, network) + msg = Text('Confirm mosaic', ui.ICON_SEND, + ui.NORMAL, 'Confirm mosaic', + ui.NORMAL, 'levy fee of', + ui.BOLD, levy_msg, + icon_color=ui.GREEN) + await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput) + + else: + msg = Text('Confirm mosaic', ui.ICON_SEND, + ui.BOLD, 'Unknown mosaic!', + ui.NORMAL, *split_words('Divisibility and levy cannot be shown for unknown mosaics', 22), + icon_color=ui.RED) + await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput) + + msg = Text('Confirm mosaic', ui.ICON_SEND, + ui.NORMAL, 'Confirm transfer of', + ui.BOLD, str(mosaic.quantity) + ' raw units', + ui.NORMAL, 'of', + ui.BOLD, mosaic.namespace + '.' + mosaic.mosaic, + icon_color=ui.GREEN) + await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput) + + +def _get_levy_msg(mosaic_definition, quantity: int, network: int) -> str: + levy_definition = get_mosaic_definition(mosaic_definition["levy_namespace"], mosaic_definition["levy_mosaic"], network) + if mosaic_definition["levy"] == NEMMosaicLevy.MosaicLevy_Absolute: + levy_fee = mosaic_definition["fee"] + else: + levy_fee = quantity * mosaic_definition["fee"] / NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE + return format_amount(levy_fee, levy_definition["divisibility"]) + levy_definition["ticker"] + + async def ask_importance_transfer(ctx, common: NEMTransactionCommon, imp: NEMImportanceTransfer): if imp.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate: m = 'Activate' @@ -51,10 +100,3 @@ async def _require_confirm_payload(ctx, payload: bytes, encrypt=False): ui.NORMAL, *split_words(payload, 22), icon_color=ui.RED) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) - - -def _mosaics_message(mosaic): - return [ui.NORMAL, 'Confirm transfer of', - ui.BOLD, str(mosaic.quantity) + ' raw units', - ui.NORMAL, 'of', - ui.BOLD, mosaic.namespace + '.' + mosaic.mosaic] diff --git a/tests/test_apps.nem.mosaic.canonicalization.py b/tests/test_apps.nem.mosaic.py similarity index 85% rename from tests/test_apps.nem.mosaic.canonicalization.py rename to tests/test_apps.nem.mosaic.py index 01c95f1c4..dde6c6ec7 100644 --- a/tests/test_apps.nem.mosaic.canonicalization.py +++ b/tests/test_apps.nem.mosaic.py @@ -1,8 +1,25 @@ from common import * from apps.nem.transfer import * +from apps.nem.mosaic.definitions import get_mosaic_definition -class TestNemMosaicCanonicalization(unittest.TestCase): +class TestNemMosaic(unittest.TestCase): + + def test_get_mosaic_definition(self): + m = get_mosaic_definition("nem", "xem", 104) + self.assertEqual(m["name"], "XEM") + self.assertEqual(m["ticker"], " XEM") + + m = get_mosaic_definition("nem", "xxx", 104) + self.assertEqual(m, None) + + m = get_mosaic_definition("aaaa", "xxx", 104) + self.assertEqual(m, None) + + m = get_mosaic_definition("pacnem", "cheese", 104) + self.assertEqual(m["name"], "PacNEM Score Tokens") + self.assertEqual(m["ticker"], " PAC:CHS") + self.assertEqual(m["fee"], 100) def test_mosaic_canonicalization(self): a = NEMMosaic()