diff --git a/_code-samples/tx-serialization/py/serialize.py b/_code-samples/tx-serialization/py/serialize.py index 53ac1c3815..c8ca3ad276 100755 --- a/_code-samples/tx-serialization/py/serialize.py +++ b/_code-samples/tx-serialization/py/serialize.py @@ -133,12 +133,13 @@ def accountid_to_bytes(address): def amount_to_bytes(a): """ - Serializes an "Amount" type, which can be either XRP or an issued currency: + Serializes an "Amount" type, which can be XRP, an issued currency, or MPT: - XRP: total 64 bits: 0, followed by 1 ("is positive"), then 0, then 61 bit UInt amount - - Issued Currency: total 384 bits: 64 bits of amount, followed by 160 bit currency code and - 160 bit issuer AccountID. - - MPT: 8-bit header with the binary value 01100000 (0x60), then 64 bit UInt amount, - 32 bit Sequence number, and 160 bit issuer AccountID. + - Issued Currency: total 384 bits: 64 bits of amount, followed by 160 bit + currency code and 160 bit issuer AccountID. + - MPT: total 264 bits: 8-bit header with the binary value 01100000 (0x60), + then 64 bit amount, 32 bit Sequence number, and 160 bit issuer AccountID. + The Sequence and issuer are adjoined as mpt_issuance_id. """ if type(a) == str: # is XRP @@ -153,17 +154,26 @@ def amount_to_bytes(a): xrp_amt = -xrp_amt return xrp_amt.to_bytes(8, byteorder="big", signed=False) elif type(a) == dict: - #TODO: handle mpt amounts - if sorted(a.keys()) != ["currency", "issuer", "value"]: + if sorted(a.keys()) == ["mpt_issuance_id", "value"]: + # MPT Amount + mpt_prefix = uint8_to_bytes(0x60) + mpt_amt = int(a["value"]) + assert mpt_amt < 2**63 and mpt_amt >= 0 + mpt_amt_bytes = mpt_amt.to_bytes(8, byteorder="big", signed=False) + mpt_issuance_id = uint192_to_bytes(a["mpt_issuance_id"]) + return mpt_prefix + mpt_amt_bytes + mpt_issuance_id + + elif sorted(a.keys()) != ["currency", "issuer", "value"]: raise ValueError("amount must have currency, value, issuer only (actually had: %s)" % sorted(a.keys())) + # Fungible token amount (non-MPT) issued_amt = IssuedAmount(a["value"]).to_bytes() logger.debug("Issued amount: %s"%issued_amt.hex()) currency_code = currency_code_to_bytes(a["currency"]) return issued_amt + currency_code + decode_address(a["issuer"]) else: - raise ValueError("amount must be XRP string or {currency, value, issuer}") + raise ValueError("amount must be XRP string, {currency, value, issuer}, or {mpt_issuance_id, value}") def array_to_bytes(array): """ @@ -389,6 +399,12 @@ def uint64_to_bytes(i): raise ValueError("UInt64 is not 64 bits long") return b +def uint192_to_bytes(i): + b = hex_to_bytes(i) + if len(b) != 24: # 24 bytes = 192 bits + raise ValueError("UInt192 is not 192 bits long") + return b + def uint384_to_bytes(i): b = hex_to_bytes(i) if len(b) != 8: # 8 bytes = 64 bits @@ -440,6 +456,7 @@ def field_to_bytes(field_name, field_val): "UInt16": uint16_to_bytes, "UInt32": uint32_to_bytes, "UInt64": uint64_to_bytes, + "UInt192": uint192_to_bytes, "UInt384": uint384_to_bytes, "Vector256": vector256_to_bytes, } diff --git a/docs/references/protocol/binary-format.md b/docs/references/protocol/binary-format.md index 199871ca05..2b2563418c 100644 --- a/docs/references/protocol/binary-format.md +++ b/docs/references/protocol/binary-format.md @@ -244,8 +244,8 @@ The _Amount_ type (also called "STAmount") is a special field type that represen Multi-Purpose Tokens (MPTs) consist of four segments in order: - 1. 8 bits indicating that this is an MPT. The most significant bit is `0` to indicate that it's not a fungible token. The second bit is `1` to indicate that it is postiive. The third most significant bit is `1` to indicate that it is an MPT. The remaining 5 bits are reserved and must all be `0`. - 2. 64 bits indicating the quantity of the MPT, as a 64-bit _signed_ integer. (However, in most contexts, negative amounts are not allowed.) + 1. 8 bits indicating that this is an MPT. The most significant bit is `0` to indicate that it's not a fungible token. The second bit is `1` to indicate that it is postiive. The third most significant bit is `1` to indicate that it is an MPT. The remaining 5 bits are reserved and must all be `0`. In other words, the first byte is `0x60`. + 2. 64 bits indicating the quantity of the MPT, as a 64-bit unsigned integer. (However, the maximum amount cannot be larger than 263-1.) 3. 32 bits indicating the `Sequence` number of the transaction that created the MPT issuance. 4. 160 bits indicating the [AccountID][] of the MPT's issuer.