Class: BinaryCodec::Issue

Inherits:
SerializedType show all
Defined in:
lib/binary-codec/types/issue.rb

Overview

An asset without an amount: XRP, an issued token, or an MPT.

The width is not fixed, and it cannot be supplied from outside. XRP is the 20 byte currency code on its own, an issued token adds the 20 byte issuer, and an MPT carries the issuer, a reserved placeholder and the four byte issuance sequence. Only the currency code says which of the three it is, so the parser has to read that before it knows how much to consume.

This class used to take a size hint and read a flat 40 bytes, which for an XRP issue swallowed the field that follows - that is why every AMM and XChain fixture decoded with a nil Asset or the wrong door account.

Constant Summary collapse

CURRENCY_LENGTH =
20
ISSUER_LENGTH =
20
SEQUENCE_LENGTH =
4
MPT_LENGTH =
CURRENCY_LENGTH + ISSUER_LENGTH + SEQUENCE_LENGTH
NO_ACCOUNT =

The reserved "no account" value. Sitting in the issuer slot, it marks the issue as an MPT rather than as a token issued by that account.

'0000000000000000000000000000000000000001'

Instance Attribute Summary

Attributes inherited from SerializedType

#bytes

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from SerializedType

from_bytes, from_hex, from_json, get_type_by_name, #to_byte_sink, #to_bytes, #to_hex, #value_of

Constructor Details

#initialize(bytes = nil) ⇒ Issue

Returns a new instance of Issue.



25
26
27
# File 'lib/binary-codec/types/issue.rb', line 25

def initialize(bytes = nil)
  super(bytes || [])
end

Class Method Details

.from(value) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/binary-codec/types/issue.rb', line 29

def self.from(value)
  return value if value.is_a?(Issue)
  return Issue.new(hex_to_bytes(value)) if value.is_a?(String)

  unless value.is_a?(::Hash)
    raise StandardError, "Cannot construct Issue from #{value.class}"
  end

  return Issue.new(mpt_bytes(value['mpt_issuance_id'])) if value['mpt_issuance_id']

  bytes = Currency.from(value['currency']).to_bytes
  bytes += AccountId.from(value['issuer']).to_bytes if value['issuer']
  Issue.new(bytes)
end

.from_parser(parser, _size_hint = nil) ⇒ Object

The size hint is accepted and ignored: callers cannot know the width, and passing one in is how the fixed 40 byte read came about.



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/binary-codec/types/issue.rb', line 57

def self.from_parser(parser, _size_hint = nil)
  return Issue.new([]) if parser.end?

  currency = parser.read(CURRENCY_LENGTH)
  return Issue.new(currency) if Currency.new(currency).to_json == 'XRP'

  issuer = parser.read(ISSUER_LENGTH)
  bytes = currency + issuer
  bytes += parser.read(SEQUENCE_LENGTH) if bytes_to_hex(issuer).upcase == NO_ACCOUNT

  Issue.new(bytes)
end

Instance Method Details

#to_json(_definitions = nil, _field_name = nil) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/binary-codec/types/issue.rb', line 70

def to_json(_definitions = nil, _field_name = nil)
  return { 'mpt_issuance_id' => mpt_issuance_id } if to_bytes.length == MPT_LENGTH

  parser = BinaryParser.new(to_hex)
  currency = Currency.from_parser(parser).to_json

  # An XRP issue has no issuer. Reporting one means 20 bytes of the next
  # field were read as an account.
  return { 'currency' => currency } if currency == 'XRP'

  { 'currency' => currency, 'issuer' => AccountId.from_parser(parser).to_json }
end