Binary serialization: implement MPT amounts in sample code

This commit is contained in:
mDuo13
2025-06-25 14:50:57 -07:00
parent b15f1392b0
commit 344d0002bb
2 changed files with 27 additions and 10 deletions

View File

@@ -133,12 +133,13 @@ def accountid_to_bytes(address):
def amount_to_bytes(a): 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 - 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 - Issued Currency: total 384 bits: 64 bits of amount, followed by 160 bit
160 bit issuer AccountID. currency code and 160 bit issuer AccountID.
- MPT: 8-bit header with the binary value 01100000 (0x60), then 64 bit UInt amount, - MPT: total 264 bits: 8-bit header with the binary value 01100000 (0x60),
32 bit Sequence number, and 160 bit issuer AccountID. 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: if type(a) == str:
# is XRP # is XRP
@@ -153,17 +154,26 @@ def amount_to_bytes(a):
xrp_amt = -xrp_amt xrp_amt = -xrp_amt
return xrp_amt.to_bytes(8, byteorder="big", signed=False) return xrp_amt.to_bytes(8, byteorder="big", signed=False)
elif type(a) == dict: elif type(a) == dict:
#TODO: handle mpt amounts if sorted(a.keys()) == ["mpt_issuance_id", "value"]:
if sorted(a.keys()) != ["currency", "issuer", "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)" % raise ValueError("amount must have currency, value, issuer only (actually had: %s)" %
sorted(a.keys())) sorted(a.keys()))
# Fungible token amount (non-MPT)
issued_amt = IssuedAmount(a["value"]).to_bytes() issued_amt = IssuedAmount(a["value"]).to_bytes()
logger.debug("Issued amount: %s"%issued_amt.hex()) logger.debug("Issued amount: %s"%issued_amt.hex())
currency_code = currency_code_to_bytes(a["currency"]) currency_code = currency_code_to_bytes(a["currency"])
return issued_amt + currency_code + decode_address(a["issuer"]) return issued_amt + currency_code + decode_address(a["issuer"])
else: 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): def array_to_bytes(array):
""" """
@@ -389,6 +399,12 @@ def uint64_to_bytes(i):
raise ValueError("UInt64 is not 64 bits long") raise ValueError("UInt64 is not 64 bits long")
return b 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): def uint384_to_bytes(i):
b = hex_to_bytes(i) b = hex_to_bytes(i)
if len(b) != 8: # 8 bytes = 64 bits if len(b) != 8: # 8 bytes = 64 bits
@@ -440,6 +456,7 @@ def field_to_bytes(field_name, field_val):
"UInt16": uint16_to_bytes, "UInt16": uint16_to_bytes,
"UInt32": uint32_to_bytes, "UInt32": uint32_to_bytes,
"UInt64": uint64_to_bytes, "UInt64": uint64_to_bytes,
"UInt192": uint192_to_bytes,
"UInt384": uint384_to_bytes, "UInt384": uint384_to_bytes,
"Vector256": vector256_to_bytes, "Vector256": vector256_to_bytes,
} }

View File

@@ -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: 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`. 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 _signed_ integer. (However, in most contexts, negative amounts are not allowed.) 2. 64 bits indicating the quantity of the MPT, as a 64-bit unsigned integer. (However, the maximum amount cannot be larger than 2<sup>63</sup>-1.)
3. 32 bits indicating the `Sequence` number of the transaction that created the MPT issuance. 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. 4. 160 bits indicating the [AccountID][] of the MPT's issuer.