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")
- MIN_XRP_DROPS =
1- MAX_XRP_DROPS =
10**17
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.
22 23 24 25 26 27 28 |
# File 'lib/binary-codec/types/amount.rb', line 22 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
251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/binary-codec/types/amount.rb', line 251 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
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/binary-codec/types/amount.rb', line 268 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
234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/binary-codec/types/amount.rb', line 234 def self.assert_xrp_is_valid(amount) if amount.include?('.') raise "#{amount} is an illegal amount" end decimal = amount.to_i unless decimal.zero? if decimal < MIN_XRP_DROPS || decimal > MAX_XRP_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.
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/binary-codec/types/amount.rb', line 37 def self.from(value) return value if value.is_a?(Amount) if value.is_a?(String) Amount.assert_xrp_is_valid(value) number = value.to_i amount_bytes = int_to_bytes(number, 8) amount_bytes[0] |= 0x40 return Amount.new(amount_bytes) end if value.respond_to?(:key?) val = value[:value] || value['value'] cur = value[:currency] || value['currency'] iss = value[:issuer] || value['issuer'] if val && cur && iss number = BigDecimal(val.to_s) if number.precision > MAX_IOU_PRECISION raise ArgumentError, 'Decimal precision out of range' end currency_inst = Currency.from(cur) issuer_inst = AccountId.from(iss) if number.zero? iou_bytes = [0x80, 0, 0, 0, 0, 0, 0, 0] return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes) end is_positive = number >= 0 abs_value = number.abs exponent = (Math.log10(abs_value.to_f).floor) - 15 mantissa = (abs_value / (BigDecimal(10)**exponent)).to_i while mantissa < 1000000000000000 mantissa *= 10 exponent -= 1 end while mantissa > 9999999999999999 mantissa /= 10 exponent += 1 end exponent_byte = exponent + 97 b1 = (is_positive ? 0x40 : 0) | 0x80 | (exponent_byte >> 2) b2 = ((exponent_byte & 0x03) << 6) | (mantissa >> 48) iou_bytes = [ b1, b2, (mantissa >> 40) & 0xff, (mantissa >> 32) & 0xff, (mantissa >> 24) & 0xff, (mantissa >> 16) & 0xff, (mantissa >> 8) & 0xff, mantissa & 0xff ] return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes) end end if value.is_a?(Integer) Amount.assert_xrp_is_valid(value.to_s) amount_bytes = int_to_bytes(value, 8) amount_bytes[0] |= 0x40 return Amount.new(amount_bytes) end raise ArgumentError, "Cannot construct Amount from the value given" end |
.from_parser(parser, _size_hint = nil) ⇒ Amount
Read an amount from a BinaryParser
Creates an Amount instance from a parser.
118 119 120 121 122 123 124 125 126 |
# File 'lib/binary-codec/types/amount.rb', line 118 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
206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/binary-codec/types/amount.rb', line 206 def self.is_amount_object_iou?(arg) return false unless arg.is_a?(::Hash) # Handle both string and symbol keys processed = arg.transform_keys(&:to_s) # Log for debugging # puts "DEBUG: Checking if #{processed.keys.inspect} is IOU" processed.key?('currency') && processed.key?('issuer') && processed.key?('value') end |
.is_amount_object_mpt?(arg) ⇒ Boolean
Type guard for AmountObjectMPT
221 222 223 224 225 226 227 228 |
# File 'lib/binary-codec/types/amount.rb', line 221 def self.is_amount_object_mpt?(arg) return false unless arg.is_a?(::Hash) keys = arg.transform_keys(&:to_s).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
293 294 295 296 297 298 299 300 |
# File 'lib/binary-codec/types/amount.rb', line 293 def self.verify_no_decimal(decimal) # p is the number of significant digits # e is the power of 10 to multiply by the mantissa to get the number # BigDecimal('1.1234567891234567').precision => 17 if decimal.precision > MAX_IOU_PRECISION raise ArgumentError, 'Decimal precision out of range' end 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.
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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/binary-codec/types/amount.rb', line 135 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 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 = mantissa * 10^exponent value = BigDecimal(mantissa_int) * (BigDecimal(10)**exponent) value = -value unless is_positive # Format the value string to match xrpl.js (stripping trailing .0) formatted_value = value.to_s('F').sub(/\.0$/, '') return { "value" => formatted_value, "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 |