xrpl-ruby 0.2.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/LICENSE +21 -0
- data/README.md +94 -0
- data/lib/address-codec/address_codec.rb +21 -4
- data/lib/address-codec/codec.rb +15 -2
- data/lib/address-codec/xrp_codec.rb +29 -2
- data/lib/binary-codec/binary_codec.rb +47 -21
- data/lib/binary-codec/enums/definitions.json +592 -1
- data/lib/binary-codec/enums/definitions.rb +23 -9
- data/lib/binary-codec/enums/fields.rb +3 -1
- data/lib/binary-codec/serdes/binary_parser.rb +44 -10
- data/lib/binary-codec/serdes/binary_serializer.rb +29 -6
- data/lib/binary-codec/serdes/bytes_list.rb +12 -1
- data/lib/binary-codec/types/account_id.rb +18 -37
- data/lib/binary-codec/types/amount.rb +123 -77
- data/lib/binary-codec/types/blob.rb +14 -5
- data/lib/binary-codec/types/currency.rb +15 -4
- data/lib/binary-codec/types/hash.rb +37 -36
- data/lib/binary-codec/types/issue.rb +47 -0
- data/lib/binary-codec/types/path_set.rb +93 -0
- data/lib/binary-codec/types/serialized_type.rb +52 -28
- data/lib/binary-codec/types/st_array.rb +106 -0
- data/lib/binary-codec/types/st_object.rb +150 -14
- data/lib/binary-codec/types/uint.rb +166 -3
- data/lib/binary-codec/types/vector256.rb +53 -0
- data/lib/binary-codec/types/xchain_bridge.rb +47 -0
- data/lib/binary-codec/utilities.rb +18 -0
- data/lib/core/base_58_xrp.rb +2 -0
- data/lib/core/base_x.rb +10 -0
- data/lib/core/core.rb +44 -6
- data/lib/core/utilities.rb +38 -0
- data/lib/key-pairs/ed25519.rb +69 -0
- data/lib/key-pairs/key_pairs.rb +92 -0
- data/lib/key-pairs/secp256k1.rb +169 -0
- data/lib/wallet/wallet.rb +179 -0
- data/lib/xrpl/client.rb +498 -0
- data/lib/xrpl/faucet.rb +138 -0
- data/lib/xrpl/version.rb +5 -0
- data/lib/xrpl-ruby.rb +30 -1
- metadata +80 -4
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require 'bigdecimal'
|
|
4
4
|
require 'bigdecimal/util'
|
|
5
5
|
|
|
6
|
-
require_relative '../utilities'
|
|
7
|
-
|
|
8
6
|
module BinaryCodec
|
|
9
7
|
class Amount < SerializedType
|
|
10
8
|
|
|
@@ -18,6 +16,8 @@ module BinaryCodec
|
|
|
18
16
|
|
|
19
17
|
MAX_DROPS = BigDecimal("1e17")
|
|
20
18
|
MIN_XRP = BigDecimal("1e-6")
|
|
19
|
+
MIN_XRP_DROPS = 1
|
|
20
|
+
MAX_XRP_DROPS = 10**17
|
|
21
21
|
|
|
22
22
|
def initialize(bytes = nil)
|
|
23
23
|
if bytes.nil?
|
|
@@ -31,70 +31,91 @@ module BinaryCodec
|
|
|
31
31
|
#
|
|
32
32
|
# @param value [Amount, Hash, String] representing the amount
|
|
33
33
|
# @return [Amount] an Amount object
|
|
34
|
+
# Creates a new Amount instance from a value.
|
|
35
|
+
# @param value [Amount, String, Hash, Integer] The value to convert.
|
|
36
|
+
# @return [Amount] The created instance.
|
|
34
37
|
def self.from(value)
|
|
35
38
|
return value if value.is_a?(Amount)
|
|
36
39
|
|
|
37
|
-
amount = Array.new(8, 0) # Equivalent to a Uint8Array of 8 zeros
|
|
38
|
-
|
|
39
40
|
if value.is_a?(String)
|
|
40
41
|
Amount.assert_xrp_is_valid(value)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
BinaryCodec.write_uint32be(int_buf[0], (number >> 32) & 0xFFFFFFFF, 0)
|
|
46
|
-
BinaryCodec.write_uint32be(int_buf[1], number & 0xFFFFFFFF, 0)
|
|
47
|
-
|
|
48
|
-
amount = int_buf.flatten
|
|
49
|
-
|
|
50
|
-
amount[0] |= 0x40
|
|
51
|
-
|
|
52
|
-
return Amount.new(amount)
|
|
42
|
+
number = value.to_i
|
|
43
|
+
amount_bytes = int_to_bytes(number, 8)
|
|
44
|
+
amount_bytes[0] |= 0x40
|
|
45
|
+
return Amount.new(amount_bytes)
|
|
53
46
|
end
|
|
54
47
|
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
if value.respond_to?(:key?)
|
|
49
|
+
val = value[:value] || value['value']
|
|
50
|
+
cur = value[:currency] || value['currency']
|
|
51
|
+
iss = value[:issuer] || value['issuer']
|
|
58
52
|
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
num = int_string.to_i
|
|
53
|
+
if val && cur && iss
|
|
54
|
+
number = BigDecimal(val.to_s)
|
|
55
|
+
|
|
56
|
+
if number.precision > MAX_IOU_PRECISION
|
|
57
|
+
raise ArgumentError, 'Decimal precision out of range'
|
|
58
|
+
end
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
BinaryCodec.write_uint32be(int_buf[1], num & 0xFFFFFFFF)
|
|
60
|
+
currency_inst = Currency.from(cur)
|
|
61
|
+
issuer_inst = AccountId.from(iss)
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
if number.zero?
|
|
64
|
+
iou_bytes = [0x80, 0, 0, 0, 0, 0, 0, 0]
|
|
65
|
+
return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes)
|
|
66
|
+
end
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
is_positive = number >= 0
|
|
69
|
+
abs_value = number.abs
|
|
70
|
+
|
|
71
|
+
exponent = (Math.log10(abs_value.to_f).floor) - 15
|
|
72
|
+
mantissa = (abs_value / (BigDecimal(10)**exponent)).to_i
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
while mantissa < 1000000000000000
|
|
75
|
+
mantissa *= 10
|
|
76
|
+
exponent -= 1
|
|
77
|
+
end
|
|
78
|
+
while mantissa > 9999999999999999
|
|
79
|
+
mantissa /= 10
|
|
80
|
+
exponent += 1
|
|
77
81
|
end
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
exponent_byte = exponent + 97
|
|
84
|
+
b1 = (is_positive ? 0x40 : 0) | 0x80 | (exponent_byte >> 2)
|
|
85
|
+
b2 = ((exponent_byte & 0x03) << 6) | (mantissa >> 48)
|
|
86
|
+
|
|
87
|
+
iou_bytes = [
|
|
88
|
+
b1, b2,
|
|
89
|
+
(mantissa >> 40) & 0xff,
|
|
90
|
+
(mantissa >> 32) & 0xff,
|
|
91
|
+
(mantissa >> 24) & 0xff,
|
|
92
|
+
(mantissa >> 16) & 0xff,
|
|
93
|
+
(mantissa >> 8) & 0xff,
|
|
94
|
+
mantissa & 0xff
|
|
95
|
+
]
|
|
96
|
+
return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes)
|
|
83
97
|
end
|
|
98
|
+
end
|
|
84
99
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
if value.is_a?(Integer)
|
|
101
|
+
Amount.assert_xrp_is_valid(value.to_s)
|
|
102
|
+
amount_bytes = int_to_bytes(value, 8)
|
|
103
|
+
amount_bytes[0] |= 0x40
|
|
104
|
+
return Amount.new(amount_bytes)
|
|
89
105
|
end
|
|
90
106
|
|
|
107
|
+
raise ArgumentError, "Cannot construct Amount from the value given"
|
|
91
108
|
end
|
|
92
109
|
|
|
93
110
|
# Read an amount from a BinaryParser
|
|
94
111
|
#
|
|
95
112
|
# @param parser [BinaryParser] The BinaryParser to read the Amount from
|
|
96
|
-
# @return [Amount] An Amount object
|
|
97
|
-
|
|
113
|
+
# @return [Amount] An Amount bundle exec rspec spec/binary-codec/types/st_object_spec.rb object
|
|
114
|
+
# Creates an Amount instance from a parser.
|
|
115
|
+
# @param parser [BinaryParser] The parser to read from.
|
|
116
|
+
# @param _size_hint [Integer, nil] Optional size hint (unused).
|
|
117
|
+
# @return [Amount] The created instance.
|
|
118
|
+
def self.from_parser(parser, _size_hint = nil)
|
|
98
119
|
is_iou = parser.peek & 0x80 != 0
|
|
99
120
|
return Amount.new(parser.read(48)) if is_iou
|
|
100
121
|
|
|
@@ -107,9 +128,13 @@ module BinaryCodec
|
|
|
107
128
|
# The JSON representation of this Amount
|
|
108
129
|
#
|
|
109
130
|
# @return [Hash, String] The JSON interpretation of this.bytes
|
|
110
|
-
|
|
131
|
+
# Returns the JSON representation of the Amount.
|
|
132
|
+
# @param _definitions [Definitions, nil] Unused.
|
|
133
|
+
# @param _field_name [String, nil] Optional field name.
|
|
134
|
+
# @return [String, Hash] The JSON representation.
|
|
135
|
+
def to_json(_definitions = nil, _field_name = nil)
|
|
111
136
|
if is_native?
|
|
112
|
-
bytes = @bytes.dup
|
|
137
|
+
bytes = @bytes.dup
|
|
113
138
|
is_positive = (bytes[0] & 0x40) != 0
|
|
114
139
|
sign = is_positive ? '' : '-'
|
|
115
140
|
bytes[0] &= 0x3f
|
|
@@ -122,47 +147,54 @@ module BinaryCodec
|
|
|
122
147
|
end
|
|
123
148
|
|
|
124
149
|
if is_iou?
|
|
125
|
-
parser = BinaryParser.new(
|
|
126
|
-
|
|
150
|
+
parser = BinaryParser.new(to_hex)
|
|
151
|
+
mantissa_bytes = parser.read(8)
|
|
127
152
|
currency = Currency.from_parser(parser)
|
|
128
153
|
issuer = AccountId.from_parser(parser)
|
|
129
154
|
|
|
130
|
-
b1 =
|
|
131
|
-
b2 =
|
|
155
|
+
b1 = mantissa_bytes[0]
|
|
156
|
+
b2 = mantissa_bytes[1]
|
|
132
157
|
|
|
133
158
|
is_positive = (b1 & 0x40) != 0
|
|
134
|
-
sign = is_positive ? '' : '-'
|
|
135
159
|
exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
|
|
136
160
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
161
|
+
mantissa_bytes[0] = 0
|
|
162
|
+
mantissa_bytes[1] &= 0x3f
|
|
163
|
+
|
|
164
|
+
# Convert mantissa bytes to integer
|
|
165
|
+
mantissa_int = mantissa_bytes.reduce(0) { |acc, b| (acc << 8) + b }
|
|
166
|
+
|
|
167
|
+
# value = mantissa * 10^exponent
|
|
168
|
+
value = BigDecimal(mantissa_int) * (BigDecimal(10)**exponent)
|
|
169
|
+
value = -value unless is_positive
|
|
170
|
+
|
|
171
|
+
# Format the value string to match xrpl.js (stripping trailing .0)
|
|
172
|
+
formatted_value = value.to_s('F').sub(/\.0$/, '')
|
|
141
173
|
|
|
142
174
|
return {
|
|
143
|
-
"value" =>
|
|
175
|
+
"value" => formatted_value,
|
|
144
176
|
"currency" => currency.to_json,
|
|
145
177
|
"issuer" => issuer.to_json
|
|
146
|
-
}
|
|
178
|
+
}
|
|
147
179
|
end
|
|
148
180
|
|
|
149
181
|
if is_mpt?
|
|
150
|
-
parser = BinaryParser.new(
|
|
182
|
+
parser = BinaryParser.new(to_hex)
|
|
151
183
|
leading_byte = parser.read(1)
|
|
152
|
-
|
|
184
|
+
amount_bytes = parser.read(8)
|
|
153
185
|
mpt_id = Hash192.from_parser(parser)
|
|
154
186
|
|
|
155
187
|
is_positive = (leading_byte[0] & 0x40) != 0
|
|
156
188
|
sign = is_positive ? '' : '-'
|
|
157
189
|
|
|
158
|
-
msb = read_uint32be(
|
|
159
|
-
lsb = read_uint32be(
|
|
190
|
+
msb = BinaryCodec.read_uint32be(amount_bytes[0, 4])
|
|
191
|
+
lsb = BinaryCodec.read_uint32be(amount_bytes[4, 4])
|
|
160
192
|
num = (msb << 32) | lsb
|
|
161
193
|
|
|
162
194
|
return {
|
|
163
|
-
value
|
|
164
|
-
mpt_issuance_id
|
|
165
|
-
}
|
|
195
|
+
"value" => "#{sign}#{num}",
|
|
196
|
+
"mpt_issuance_id" => mpt_id.to_hex
|
|
197
|
+
}
|
|
166
198
|
end
|
|
167
199
|
|
|
168
200
|
raise 'Invalid amount to construct JSON'
|
|
@@ -172,17 +204,23 @@ module BinaryCodec
|
|
|
172
204
|
|
|
173
205
|
# Type guard for AmountObjectIOU
|
|
174
206
|
def self.is_amount_object_iou?(arg)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
207
|
+
return false unless arg.is_a?(::Hash)
|
|
208
|
+
|
|
209
|
+
# Handle both string and symbol keys
|
|
210
|
+
processed = arg.transform_keys(&:to_s)
|
|
211
|
+
|
|
212
|
+
# Log for debugging
|
|
213
|
+
# puts "DEBUG: Checking if #{processed.keys.inspect} is IOU"
|
|
214
|
+
|
|
215
|
+
processed.key?('currency') &&
|
|
216
|
+
processed.key?('issuer') &&
|
|
217
|
+
processed.key?('value')
|
|
181
218
|
end
|
|
182
219
|
|
|
183
220
|
# Type guard for AmountObjectMPT
|
|
184
221
|
def self.is_amount_object_mpt?(arg)
|
|
185
|
-
|
|
222
|
+
return false unless arg.is_a?(::Hash)
|
|
223
|
+
keys = arg.transform_keys(&:to_s).keys.sort
|
|
186
224
|
|
|
187
225
|
keys.length == 2 &&
|
|
188
226
|
keys[0] == 'mpt_issuance_id' &&
|
|
@@ -198,9 +236,9 @@ module BinaryCodec
|
|
|
198
236
|
raise "#{amount} is an illegal amount"
|
|
199
237
|
end
|
|
200
238
|
|
|
201
|
-
decimal =
|
|
239
|
+
decimal = amount.to_i
|
|
202
240
|
unless decimal.zero?
|
|
203
|
-
if decimal <
|
|
241
|
+
if decimal < MIN_XRP_DROPS || decimal > MAX_XRP_DROPS
|
|
204
242
|
raise "#{amount} is an illegal amount"
|
|
205
243
|
end
|
|
206
244
|
end
|
|
@@ -247,21 +285,25 @@ module BinaryCodec
|
|
|
247
285
|
# Ensure that the value, after being multiplied by the exponent, does not
|
|
248
286
|
# contain a decimal. This function is typically used to validate numbers
|
|
249
287
|
# that need to be represented as precise integers after scaling, such as
|
|
250
|
-
# amounts in financial transactions.
|
|
288
|
+
# amounts in financial transactions. Example failure:1.1234567891234567
|
|
251
289
|
#
|
|
252
290
|
# @param decimal [BigDecimal] A BigDecimal object
|
|
253
291
|
# @raise [ArgumentError] if the value contains a decimal
|
|
254
292
|
# @return [String] The decimal converted to a string without a decimal point
|
|
255
293
|
def self.verify_no_decimal(decimal)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
294
|
+
# p is the number of significant digits
|
|
295
|
+
# e is the power of 10 to multiply by the mantissa to get the number
|
|
296
|
+
# BigDecimal('1.1234567891234567').precision => 17
|
|
297
|
+
if decimal.precision > MAX_IOU_PRECISION
|
|
298
|
+
raise ArgumentError, 'Decimal precision out of range'
|
|
299
|
+
end
|
|
260
300
|
end
|
|
261
301
|
|
|
262
302
|
# Check if this amount is in units of Native Currency (XRP)
|
|
263
303
|
#
|
|
264
304
|
# @return [Boolean] true if Native (XRP)
|
|
305
|
+
# Checks if the amount is a native XRP amount.
|
|
306
|
+
# @return [Boolean] True if native, false otherwise.
|
|
265
307
|
def is_native?
|
|
266
308
|
(self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20).zero?
|
|
267
309
|
end
|
|
@@ -269,6 +311,8 @@ module BinaryCodec
|
|
|
269
311
|
# Check if this amount is in units of MPT
|
|
270
312
|
#
|
|
271
313
|
# @return [Boolean] true if MPT
|
|
314
|
+
# Checks if the amount is a multi-purpose token (MPT).
|
|
315
|
+
# @return [Boolean] True if MPT, false otherwise.
|
|
272
316
|
def is_mpt?
|
|
273
317
|
(self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20) != 0
|
|
274
318
|
end
|
|
@@ -276,6 +320,8 @@ module BinaryCodec
|
|
|
276
320
|
# Check if this amount is in units of IOU
|
|
277
321
|
#
|
|
278
322
|
# @return [Boolean] true if IOU
|
|
323
|
+
# Checks if the amount is an IOU amount.
|
|
324
|
+
# @return [Boolean] True if IOU, false otherwise.
|
|
279
325
|
def is_iou?
|
|
280
326
|
(self.bytes[0] & 0x80) != 0
|
|
281
327
|
end
|
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../../core/core'
|
|
4
|
-
|
|
5
3
|
module BinaryCodec
|
|
6
4
|
class Blob < SerializedType
|
|
7
5
|
|
|
8
6
|
def initialize(byte_buf = nil)
|
|
9
|
-
|
|
7
|
+
super(byte_buf || [])
|
|
10
8
|
end
|
|
11
9
|
|
|
10
|
+
# Creates a new Blob instance from a value.
|
|
11
|
+
# @param value [Blob, String, Array<Integer>] The value to convert.
|
|
12
|
+
# @return [Blob] The created instance.
|
|
12
13
|
def self.from(value)
|
|
13
14
|
return value if value.is_a?(Blob)
|
|
14
15
|
|
|
15
16
|
if value.is_a?(String)
|
|
16
|
-
|
|
17
|
+
unless valid_hex?(value)
|
|
17
18
|
raise StandardError, 'Cannot construct Blob from a non-hex string'
|
|
18
19
|
end
|
|
19
20
|
return Blob.new(hex_to_bytes(value))
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
if value.is_a?(::Array)
|
|
24
|
+
return Blob.new(value)
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
raise StandardError, 'Cannot construct Blob from value given'
|
|
23
28
|
end
|
|
24
29
|
|
|
30
|
+
# Creates a Blob instance from a parser.
|
|
31
|
+
# @param parser [BinaryParser] The parser to read from.
|
|
32
|
+
# @param hint [Integer, nil] Optional width hint.
|
|
33
|
+
# @return [Blob] The created instance.
|
|
25
34
|
def self.from_parser(parser, hint = nil)
|
|
26
|
-
|
|
35
|
+
new(parser.read(hint))
|
|
27
36
|
end
|
|
28
37
|
|
|
29
38
|
end
|
|
@@ -15,8 +15,8 @@ module BinaryCodec
|
|
|
15
15
|
@width = 20
|
|
16
16
|
|
|
17
17
|
def initialize(byte_buf = nil)
|
|
18
|
-
super(byte_buf
|
|
19
|
-
hex =
|
|
18
|
+
super(byte_buf)
|
|
19
|
+
hex = to_hex
|
|
20
20
|
|
|
21
21
|
if XRP_HEX_REGEX.match?(hex)
|
|
22
22
|
@_iso = 'XRP'
|
|
@@ -27,20 +27,31 @@ module BinaryCodec
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Returns the ISO code of the currency, if applicable.
|
|
31
|
+
# @return [String, nil] The ISO code.
|
|
30
32
|
def iso
|
|
31
33
|
@_iso
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
# Creates a new Currency instance from a value.
|
|
37
|
+
# @param value [Currency, String, Array<Integer>] The value to convert.
|
|
38
|
+
# @return [Currency] The created instance.
|
|
34
39
|
def self.from(value)
|
|
35
40
|
return value if value.is_a?(Currency)
|
|
36
41
|
|
|
37
42
|
if value.is_a?(String)
|
|
38
|
-
return
|
|
43
|
+
return new(bytes_from_representation(value))
|
|
39
44
|
end
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
if value.is_a?(::Array)
|
|
47
|
+
return new(value)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
raise StandardError, "Cannot construct Currency from #{value.class}"
|
|
42
51
|
end
|
|
43
52
|
|
|
53
|
+
# Returns the JSON representation of the currency.
|
|
54
|
+
# @return [String] The ISO code or hex string.
|
|
44
55
|
def to_json
|
|
45
56
|
iso = self.iso
|
|
46
57
|
return iso unless iso.nil?
|
|
@@ -2,24 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
module BinaryCodec
|
|
4
4
|
class Hash < ComparableSerializedType
|
|
5
|
+
# Returns the width of the Hash type in bytes.
|
|
6
|
+
# @return [Integer] The width.
|
|
7
|
+
def self.width
|
|
8
|
+
@width
|
|
9
|
+
end
|
|
5
10
|
|
|
6
|
-
def initialize(bytes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if bytes.length !=
|
|
10
|
-
|
|
11
|
-
raise StandardError, "Invalid Hash length #{bytes.length}"
|
|
11
|
+
def initialize(bytes = nil)
|
|
12
|
+
bytes = Array.new(self.class.width, 0) if bytes.nil? || bytes.empty?
|
|
13
|
+
super(bytes)
|
|
14
|
+
if @bytes.length != self.class.width
|
|
15
|
+
raise StandardError, "Invalid Hash length #{@bytes.length}"
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
# Creates a new Hash instance from a value.
|
|
20
|
+
# @param value [Hash, String, Array<Integer>] The value to convert.
|
|
21
|
+
# @return [Hash] The created instance.
|
|
22
|
+
def self.from(value)
|
|
23
|
+
return value if value.is_a?(self)
|
|
24
|
+
|
|
25
|
+
if value.is_a?(String)
|
|
26
|
+
return new if value.empty?
|
|
27
|
+
return new(hex_to_bytes(value))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if value.is_a?(::Array)
|
|
31
|
+
return new(value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
raise StandardError, "Cannot construct #{self} from the value given"
|
|
17
35
|
end
|
|
18
36
|
|
|
37
|
+
# Creates a Hash instance from a parser.
|
|
38
|
+
# @param parser [BinaryParser] The parser to read from.
|
|
39
|
+
# @param hint [Integer, nil] Optional width hint.
|
|
40
|
+
# @return [Hash] The created instance.
|
|
19
41
|
def self.from_parser(parser, hint = nil)
|
|
20
42
|
new(parser.read(hint || width))
|
|
21
43
|
end
|
|
22
44
|
|
|
45
|
+
# Compares this Hash to another Hash.
|
|
46
|
+
# @param other [Hash] The other Hash to compare to.
|
|
47
|
+
# @return [Integer] Comparison result (-1, 0, or 1).
|
|
23
48
|
def compare_to(other)
|
|
24
49
|
@bytes <=> other.bytes
|
|
25
50
|
end
|
|
@@ -42,15 +67,9 @@ module BinaryCodec
|
|
|
42
67
|
|
|
43
68
|
class Hash128 < Hash
|
|
44
69
|
@width = 16
|
|
45
|
-
@zero_128 = [0] * @width # Array.new(@width, 0)
|
|
46
|
-
|
|
47
|
-
class << self
|
|
48
|
-
attr_reader :width, :zero_128
|
|
49
|
-
end
|
|
50
70
|
|
|
51
71
|
def initialize(bytes = nil)
|
|
52
|
-
bytes
|
|
53
|
-
super(bytes, self.class.width)
|
|
72
|
+
super(bytes)
|
|
54
73
|
end
|
|
55
74
|
|
|
56
75
|
def to_hex
|
|
@@ -62,43 +81,25 @@ module BinaryCodec
|
|
|
62
81
|
|
|
63
82
|
class Hash160 < Hash
|
|
64
83
|
@width = 20
|
|
65
|
-
@zero_160 = [0] * @width # Array.new(@width, 0)
|
|
66
|
-
|
|
67
|
-
class << self
|
|
68
|
-
attr_reader :width, :zero_160
|
|
69
|
-
end
|
|
70
84
|
|
|
71
85
|
def initialize(bytes = nil)
|
|
72
|
-
bytes
|
|
73
|
-
super(bytes, self.class.width)
|
|
86
|
+
super(bytes)
|
|
74
87
|
end
|
|
75
88
|
end
|
|
76
89
|
|
|
77
90
|
class Hash192 < Hash
|
|
78
91
|
@width = 24
|
|
79
|
-
@zero_192 = [0] * @width # Array.new(@width, 0)
|
|
80
|
-
|
|
81
|
-
class << self
|
|
82
|
-
attr_reader :width, :zero_192
|
|
83
|
-
end
|
|
84
92
|
|
|
85
93
|
def initialize(bytes = nil)
|
|
86
|
-
bytes
|
|
87
|
-
super(bytes, self.class.width)
|
|
94
|
+
super(bytes)
|
|
88
95
|
end
|
|
89
96
|
end
|
|
90
97
|
|
|
91
98
|
class Hash256 < Hash
|
|
92
99
|
@width = 32
|
|
93
|
-
@zero_256 = [0] * @width # Array.new(@width, 0)
|
|
94
|
-
|
|
95
|
-
class << self
|
|
96
|
-
attr_reader :width, :zero_256
|
|
97
|
-
end
|
|
98
100
|
|
|
99
101
|
def initialize(bytes = nil)
|
|
100
|
-
bytes
|
|
101
|
-
super(bytes, self.class.width)
|
|
102
|
+
super(bytes)
|
|
102
103
|
end
|
|
103
104
|
end
|
|
104
105
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BinaryCodec
|
|
4
|
+
class Issue < SerializedType
|
|
5
|
+
def initialize(bytes = nil)
|
|
6
|
+
super(bytes || [])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.from(value)
|
|
10
|
+
return value if value.is_a?(Issue)
|
|
11
|
+
|
|
12
|
+
if value.is_a?(String)
|
|
13
|
+
return Issue.new(hex_to_bytes(value))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if value.is_a?(Hash) || value.is_a?(::Hash)
|
|
17
|
+
bytes = []
|
|
18
|
+
bytes.concat(Currency.from(value['currency']).to_bytes)
|
|
19
|
+
bytes.concat(AccountId.from(value['issuer']).to_bytes) if value['issuer']
|
|
20
|
+
return Issue.new(bytes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
raise StandardError, "Cannot construct Issue from #{value.class}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.from_parser(parser, size_hint = nil)
|
|
27
|
+
bytes = []
|
|
28
|
+
return Issue.new(bytes) if parser.end?
|
|
29
|
+
bytes.concat(parser.read(20)) # Currency
|
|
30
|
+
unless parser.end? || (size_hint && size_hint <= 20)
|
|
31
|
+
bytes.concat(parser.read(20))
|
|
32
|
+
end
|
|
33
|
+
Issue.new(bytes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_json(_definitions = nil, _field_name = nil)
|
|
37
|
+
parser = BinaryParser.new(to_hex)
|
|
38
|
+
result = {}
|
|
39
|
+
result['currency'] = Currency.from_parser(parser).to_json
|
|
40
|
+
result['issuer'] = AccountId.from_parser(parser).to_json unless parser.end?
|
|
41
|
+
result
|
|
42
|
+
rescue
|
|
43
|
+
# Fallback for partial/invalid binary
|
|
44
|
+
{ 'currency' => Currency.new(to_bytes[0, 20]).to_json }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BinaryCodec
|
|
4
|
+
class PathSet < SerializedType
|
|
5
|
+
PATH_STEP_END_MARKER = 0xFF
|
|
6
|
+
PATH_END_MARKER = 0xFE
|
|
7
|
+
|
|
8
|
+
TYPE_ACCOUNT = 0x01
|
|
9
|
+
TYPE_CURRENCY = 0x10
|
|
10
|
+
TYPE_ISSUER = 0x20
|
|
11
|
+
|
|
12
|
+
def initialize(bytes = nil)
|
|
13
|
+
super(bytes || [])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.from(value)
|
|
17
|
+
return value if value.is_a?(PathSet)
|
|
18
|
+
|
|
19
|
+
if value.is_a?(String)
|
|
20
|
+
return PathSet.new(hex_to_bytes(value))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if value.is_a?(::Array)
|
|
24
|
+
bytes = []
|
|
25
|
+
value.each_with_index do |path, index|
|
|
26
|
+
path.each do |step|
|
|
27
|
+
type = 0
|
|
28
|
+
step_bytes = []
|
|
29
|
+
|
|
30
|
+
if step['account']
|
|
31
|
+
type |= TYPE_ACCOUNT
|
|
32
|
+
step_bytes.concat(AccountId.from(step['account']).to_bytes)
|
|
33
|
+
end
|
|
34
|
+
if step['currency']
|
|
35
|
+
type |= TYPE_CURRENCY
|
|
36
|
+
step_bytes.concat(Currency.from(step['currency']).to_bytes)
|
|
37
|
+
end
|
|
38
|
+
if step['issuer']
|
|
39
|
+
type |= TYPE_ISSUER
|
|
40
|
+
step_bytes.concat(AccountId.from(step['issuer']).to_bytes)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
bytes << type
|
|
44
|
+
bytes.concat(step_bytes)
|
|
45
|
+
end
|
|
46
|
+
bytes << (index == value.length - 1 ? PATH_END_MARKER : PATH_STEP_END_MARKER)
|
|
47
|
+
end
|
|
48
|
+
return PathSet.new(bytes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
raise StandardError, "Cannot construct PathSet from #{value.class}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.from_parser(parser, _hint = nil)
|
|
55
|
+
bytes = []
|
|
56
|
+
loop do
|
|
57
|
+
type = parser.read_uint8
|
|
58
|
+
bytes << type
|
|
59
|
+
break if type == PATH_END_MARKER
|
|
60
|
+
|
|
61
|
+
if type != PATH_STEP_END_MARKER
|
|
62
|
+
bytes.concat(parser.read(20)) if (type & TYPE_ACCOUNT) != 0
|
|
63
|
+
bytes.concat(parser.read(20)) if (type & TYPE_CURRENCY) != 0
|
|
64
|
+
bytes.concat(parser.read(20)) if (type & TYPE_ISSUER) != 0
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
PathSet.new(bytes)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_json(_definitions = nil, _field_name = nil)
|
|
71
|
+
parser = BinaryParser.new(to_hex)
|
|
72
|
+
paths = []
|
|
73
|
+
current_path = []
|
|
74
|
+
|
|
75
|
+
until parser.end?
|
|
76
|
+
type = parser.read_uint8
|
|
77
|
+
if type == PATH_END_MARKER || type == PATH_STEP_END_MARKER
|
|
78
|
+
paths << current_path
|
|
79
|
+
current_path = []
|
|
80
|
+
break if type == PATH_END_MARKER
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
step = {}
|
|
85
|
+
step['account'] = AccountId.from_parser(parser).to_json if (type & TYPE_ACCOUNT) != 0
|
|
86
|
+
step['currency'] = Currency.from_parser(parser).to_json if (type & TYPE_CURRENCY) != 0
|
|
87
|
+
step['issuer'] = AccountId.from_parser(parser).to_json if (type & TYPE_ISSUER) != 0
|
|
88
|
+
current_path << step
|
|
89
|
+
end
|
|
90
|
+
paths
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|