Class: BinaryCodec::Amount
- Inherits:
-
SerializedType
- Object
- SerializedType
- BinaryCodec::Amount
- Defined in:
- lib/binary-codec/types/amount.rb
Constant Summary collapse
- DEFAULT_AMOUNT_HEX =
"4000000000000000".freeze
- ZERO_CURRENCY_AMOUNT_HEX =
"8000000000000000".freeze
- NATIVE_AMOUNT_BYTE_LENGTH =
8- CURRENCY_AMOUNT_BYTE_LENGTH =
48- MAX_IOU_PRECISION =
16- MIN_IOU_EXPONENT =
-96- MAX_IOU_EXPONENT =
80- MAX_DROPS =
BigDecimal("1e17")
- MIN_XRP =
BigDecimal("1e-6")
Instance Attribute Summary
Attributes inherited from SerializedType
Class Method Summary collapse
-
.assert_iou_is_valid(decimal) ⇒ Object
Validate IOU.value amount.
-
.assert_mpt_is_valid(amount) ⇒ void
Validate MPT.value amount.
-
.assert_xrp_is_valid(amount) ⇒ void
Validate XRP amount.
-
.from(value) ⇒ Amount
Construct an amount from an IOU, MPT, or string amount.
-
.from_parser(parser, _size_hint = nil) ⇒ Amount
Read an amount from a BinaryParser.
-
.is_amount_object_iou?(arg) ⇒ Boolean
Type guard for AmountObjectIOU.
-
.is_amount_object_mpt?(arg) ⇒ Boolean
Type guard for AmountObjectMPT.
-
.verify_no_decimal(decimal) ⇒ String
Ensure that the value, after being multiplied by the exponent, does not contain a decimal.
Instance Method Summary collapse
-
#initialize(bytes = nil) ⇒ Amount
constructor
A new instance of Amount.
-
#to_json(_definitions = nil, _field_name = nil) ⇒ Hash, String
The JSON representation of this Amount.
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) ⇒ Amount
Returns a new instance of Amount.
20 21 22 23 24 25 26 |
# File 'lib/binary-codec/types/amount.rb', line 20 def initialize(bytes = nil) if bytes.nil? bytes = hex_to_bytes(DEFAULT_AMOUNT_HEX) end @bytes = bytes end |
Class Method Details
.assert_iou_is_valid(decimal) ⇒ Object
Validate IOU.value amount
227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/binary-codec/types/amount.rb', line 227 def self.assert_iou_is_valid(decimal) return if decimal.zero? p = decimal.precision e = (decimal.exponent || 0) - 15 if p > MAX_IOU_PRECISION || e > MAX_IOU_EXPONENT || e < MIN_IOU_EXPONENT raise ArgumentError, 'Decimal precision out of range' end verify_no_decimal(decimal) end |
.assert_mpt_is_valid(amount) ⇒ void
This method returns an undefined value.
Validate MPT.value amount
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/binary-codec/types/amount.rb', line 244 def self.assert_mpt_is_valid(amount) if amount.include?('.') raise "#{amount} is an illegal amount" end decimal = BigDecimal(amount) unless decimal.zero? if decimal < BigDecimal("0") raise "#{amount} is an illegal amount" end if (amount.to_i & mpt_mask) != 0 raise "#{amount} is an illegal amount" end end end |
.assert_xrp_is_valid(amount) ⇒ void
This method returns an undefined value.
Validate XRP amount
210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/binary-codec/types/amount.rb', line 210 def self.assert_xrp_is_valid(amount) if amount.include?('.') raise "#{amount} is an illegal amount" end decimal = BigDecimal(amount) unless decimal.zero? if decimal < MIN_XRP || decimal > MAX_DROPS raise "#{amount} is an illegal amount" end end end |
.from(value) ⇒ Amount
Construct an amount from an IOU, MPT, or string amount
Creates a new Amount instance from a value.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/binary-codec/types/amount.rb', line 35 def self.from(value) return value if value.is_a?(Amount) amount = Array.new(8, 0) # Equivalent to a Uint8Array of 8 zeros if value.is_a?(String) Amount.assert_xrp_is_valid(value) number = value.to_i # Use to_i for equivalent BigInt handling int_buf = [Array.new(4, 0), Array.new(4, 0)] BinaryCodec.write_uint32be(int_buf[0], (number >> 32) & 0xFFFFFFFF, 0) BinaryCodec.write_uint32be(int_buf[1], number & 0xFFFFFFFF, 0) amount = int_buf.flatten amount[0] |= 0x40 return Amount.new(amount) end if is_amount_object_iou?(value) number = BigDecimal(value[:value]) self.assert_iou_is_valid(number) if number.zero? amount[0] |= 0x80 else scale = number.frac.to_s('F').split('.').last.size unscaled_value = (number * (10**scale)).to_i int_string = unscaled_value.abs.to_s.ljust(16, '0') num = int_string.to_i int_buf = [Array.new(4, 0), Array.new(4, 0)] BinaryCodec.write_uint32be(int_buf[0], (num >> 32) & 0xFFFFFFFF) BinaryCodec.write_uint32be(int_buf[1], num & 0xFFFFFFFF) amount = int_buf.flatten amount[0] |= 0x80 if number > 0 amount[0] |= 0x40 end exponent = number.exponent - 16 exponent_byte = 97 + exponent amount[0] |= exponent_byte >> 2 amount[1] |= (exponent_byte & 0x03) << 6 end currency = Currency.from(value[:currency]).to_bytes issuer = AccountId.from(value[:issuer]).to_bytes return Amount.new(amount + currency + issuer) end end |
.from_parser(parser, _size_hint = nil) ⇒ Amount
Read an amount from a BinaryParser
Creates an Amount instance from a parser.
102 103 104 105 106 107 108 109 110 |
# File 'lib/binary-codec/types/amount.rb', line 102 def self.from_parser(parser, _size_hint = nil) is_iou = parser.peek & 0x80 != 0 return Amount.new(parser.read(48)) if is_iou # The amount can be either MPT or XRP at this point is_mpt = parser.peek & 0x20 != 0 num_bytes = is_mpt ? 33 : 8 Amount.new(parser.read(num_bytes)) end |
.is_amount_object_iou?(arg) ⇒ Boolean
Type guard for AmountObjectIOU
188 189 190 191 192 193 194 195 |
# File 'lib/binary-codec/types/amount.rb', line 188 def self.is_amount_object_iou?(arg) keys = arg.transform_keys(&:to_s).keys.sort keys.length == 3 && keys[0] == 'currency' && keys[1] == 'issuer' && keys[2] == 'value' end |
.is_amount_object_mpt?(arg) ⇒ Boolean
Type guard for AmountObjectMPT
198 199 200 201 202 203 204 |
# File 'lib/binary-codec/types/amount.rb', line 198 def self.is_amount_object_mpt?(arg) keys = arg.keys.sort keys.length == 2 && keys[0] == 'mpt_issuance_id' && keys[1] == 'value' end |
.verify_no_decimal(decimal) ⇒ String
Ensure that the value, after being multiplied by the exponent, does not contain a decimal. This function is typically used to validate numbers that need to be represented as precise integers after scaling, such as amounts in financial transactions. Example failure:1.1234567891234567
269 270 271 272 273 274 |
# File 'lib/binary-codec/types/amount.rb', line 269 def self.verify_no_decimal(decimal) exponent = -((decimal.exponent || 0) - 16) scaled_decimal = decimal * 10 ** exponent raise ArgumentError, 'Decimal place found in int_string' unless scaled_decimal.frac == 0 end |
Instance Method Details
#to_json(_definitions = nil, _field_name = nil) ⇒ Hash, String
The JSON representation of this Amount
Returns the JSON representation of the Amount.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/binary-codec/types/amount.rb', line 119 def to_json(_definitions = nil, _field_name = nil) if is_native? bytes = @bytes.dup is_positive = (bytes[0] & 0x40) != 0 sign = is_positive ? '' : '-' bytes[0] &= 0x3f msb = BinaryCodec.read_uint32be(bytes[0, 4]) lsb = BinaryCodec.read_uint32be(bytes[4, 4]) num = (msb << 32) | lsb return "#{sign}#{num}" end if is_iou? parser = BinaryParser.new(to_hex) mantissa_bytes = parser.read(8) currency = Currency.from_parser(parser) issuer = AccountId.from_parser(parser) b1 = mantissa_bytes[0] b2 = mantissa_bytes[1] is_positive = (b1 & 0x40) != 0 sign = is_positive ? '' : '-' exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97 mantissa_bytes[0] = 0 mantissa_bytes[1] &= 0x3f # Convert mantissa bytes to integer mantissa_int = mantissa_bytes.reduce(0) { |acc, b| (acc << 8) + b } value = BigDecimal(mantissa_int) * (BigDecimal(10)**exponent) value = -value unless is_positive self.class.assert_iou_is_valid(value) return { "value" => value.to_s('F').sub(/\.0$/, ''), "currency" => currency.to_json, "issuer" => issuer.to_json } end if is_mpt? parser = BinaryParser.new(to_hex) leading_byte = parser.read(1) amount_bytes = parser.read(8) mpt_id = Hash192.from_parser(parser) is_positive = (leading_byte[0] & 0x40) != 0 sign = is_positive ? '' : '-' msb = BinaryCodec.read_uint32be(amount_bytes[0, 4]) lsb = BinaryCodec.read_uint32be(amount_bytes[4, 4]) num = (msb << 32) | lsb return { "value" => "#{sign}#{num}", "mpt_issuance_id" => mpt_id.to_hex } end raise 'Invalid amount to construct JSON' end |