scale_rb 0.1.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.
data/lib/codec.rb ADDED
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: set, bitvec
4
+
5
+ def bytes?(type)
6
+ type.downcase == 'bytes'
7
+ end
8
+
9
+ def boolean?(type)
10
+ type.downcase == 'bool' || type.downcase == 'boolean'
11
+ end
12
+
13
+ def string?(type)
14
+ type.downcase == 'str' || type.downcase == 'string' || type.downcase == 'text'
15
+ end
16
+
17
+ def compact?(type)
18
+ type.downcase == 'compact' ||
19
+ (type[0..7].downcase == 'compact<' && type[-1] == '>')
20
+ end
21
+
22
+ def int?(type)
23
+ type[0].downcase == 'i' && type[1..] =~ /\A(8|16|32|64|128|256|512)\z/
24
+ end
25
+
26
+ def uint?(type)
27
+ type[0].downcase == 'u' && type[1..] =~ /\A(8|16|32|64|128|256|512)\z/
28
+ end
29
+
30
+ # def bitvec?(type)
31
+ # end
32
+
33
+ def option?(type)
34
+ type[0..6].downcase == 'option<' && type[-1] == '>'
35
+ end
36
+
37
+ def array?(type)
38
+ type[0] == '[' && type[-1] == ']'
39
+ end
40
+
41
+ def vec?(type)
42
+ type[0..3].downcase == 'vec<' && type[-1] == '>'
43
+ end
44
+
45
+ def tuple?(type)
46
+ type[0] == '(' && type[-1] == ')'
47
+ end
48
+
49
+ def struct?(type)
50
+ type.instance_of?(Hash)
51
+ end
52
+
53
+ def enum?(type)
54
+ type.instance_of?(Hash) && type.key?(:_enum)
55
+ end
56
+
57
+ def parse_fixed_array(type)
58
+ scan_out = type.scan(/\A\[\s*(.+)\s*;\s*(\d+)\s*\]\z/)
59
+ raise ScaleRb::TypeParseError, type if scan_out.empty?
60
+ raise ScaleRb::TypeParseError, type if scan_out[0].length != 2
61
+
62
+ inner_type = scan_out[0][0]
63
+ length = scan_out[0][1].to_i
64
+ [inner_type, length]
65
+ end
66
+
67
+ module ScaleRb
68
+ class Error < StandardError; end
69
+ class NotImplemented < Error; end
70
+ class NilTypeError < Error; end
71
+ class TypeParseError < Error; end
72
+ class NotEnoughBytesError < Error; end
73
+ class InvalidBytesError < Error; end
74
+ class Unreachable < Error; end
75
+ class IndexOutOfRangeError < Error; end
76
+ class LengthNotEqualErr < Error; end
77
+ class InvalidValueError < Error; end
78
+
79
+ class << self
80
+ def decode(type, bytes, registry = {})
81
+ logger.debug '--------------------------------------------------'
82
+ debug 'decoding type', type
83
+ debug 'bytes', bytes&.length
84
+
85
+ if type.instance_of?(String)
86
+ return decode_bytes(bytes) if bytes?(type) # Bytes
87
+ return decode_boolean(bytes) if boolean?(type) # Boolean
88
+ return decode_string(bytes) if string?(type) # String
89
+ return decode_int(type, bytes) if int?(type) # i8, i16...
90
+ return decode_uint(type, bytes) if uint?(type) # u8, u16...
91
+ return decode_compact(bytes) if compact?(type) # Compact<>
92
+ return decode_option(type, bytes, registry) if option?(type) # Option<>
93
+ return decode_array(type, bytes, registry) if array?(type) # [u8; 3]
94
+ return decode_vec(type, bytes, registry) if vec?(type) # Vec<u8>
95
+ return decode_tuple(type, bytes, registry) if tuple?(type) # (u8, u8)
96
+
97
+ # search the type from registry if not the types above
98
+ registry_type = get_final_type_from_registry(registry, type)
99
+ return decode(registry_type, bytes, registry) if registry_type
100
+ elsif type.instance_of?(Hash)
101
+ return decode_enum(type, bytes, registry) if enum?(type)
102
+ return decode_struct(type, bytes, registry) if struct?(type)
103
+ end
104
+
105
+ raise NotImplemented, "type: #{type.inspect}"
106
+ end
107
+
108
+ def decode_bytes(bytes)
109
+ length, remaining_bytes = do_decode_compact(bytes)
110
+ value = remaining_bytes[0...length].to_hex
111
+ debug 'length', length
112
+ debug 'value', value
113
+ [
114
+ value,
115
+ remaining_bytes[length..]
116
+ ]
117
+ end
118
+
119
+ def decode_boolean(bytes)
120
+ value =
121
+ if bytes[0] == 0x00
122
+ false
123
+ elsif bytes[0] == 0x01
124
+ true
125
+ else
126
+ raise InvalidBytesError, 'type: Boolean'
127
+ end
128
+ debug 'value', value
129
+ [value, bytes[1..]]
130
+ end
131
+
132
+ def decode_string(bytes)
133
+ length, remaining_bytes = do_decode_compact(bytes)
134
+ raise NotEnoughBytesError, 'type: String' if remaining_bytes.length < length
135
+
136
+ value = remaining_bytes[0...length].to_utf8
137
+ debug 'byte length', length
138
+ debug 'value', value.inspect
139
+ [
140
+ value,
141
+ remaining_bytes[length..]
142
+ ]
143
+ end
144
+
145
+ def decode_int(type, bytes)
146
+ bit_length = type[1..].to_i
147
+ byte_length = bit_length / 8
148
+ raise NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
149
+
150
+ value = bytes[0...byte_length].flip.to_int(bit_length)
151
+ debug 'value', value
152
+ [
153
+ value,
154
+ bytes[byte_length..]
155
+ ]
156
+ end
157
+
158
+ def decode_uint(type, bytes)
159
+ bit_length = type[1..].to_i
160
+ byte_length = bit_length / 8
161
+ raise NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
162
+
163
+ value = bytes[0...byte_length].flip.to_uint
164
+ debug 'value', value
165
+ [
166
+ value,
167
+ bytes[byte_length..]
168
+ ]
169
+ end
170
+
171
+ def decode_compact(bytes)
172
+ result = do_decode_compact(bytes)
173
+ debug 'value', result[0]
174
+ result
175
+ end
176
+
177
+ def decode_option(type, bytes, registry = {})
178
+ inner_type = type.scan(/\A[O|o]ption<(.+)>\z/).first.first
179
+
180
+ return [nil, bytes[1..]] if bytes[0] == 0x00
181
+ return decode(inner_type, bytes[1..], registry) if bytes[0] == 0x01
182
+
183
+ raise InvalidBytesError, "type: #{type}"
184
+ end
185
+
186
+ def decode_array(type, bytes, registry = {})
187
+ inner_type, length = parse_fixed_array(type)
188
+ _decode_types([inner_type] * length, bytes, registry)
189
+ end
190
+
191
+ def decode_vec(type, bytes, registry = {})
192
+ inner_type = type.scan(/\A[V|v]ec<(.+)>\z/).first.first
193
+ length, remaining_bytes = do_decode_compact(bytes)
194
+ debug 'length', length
195
+ _decode_types([inner_type] * length, remaining_bytes, registry)
196
+ end
197
+
198
+ def decode_tuple(tuple_type, bytes, registry = {})
199
+ inner_types = tuple_type.scan(/\A\(\s*(.+)\s*\)\z/)[0][0].split(',').map(&:strip)
200
+ _decode_types(inner_types, bytes, registry)
201
+ end
202
+
203
+ # TODO: custrom index
204
+ def decode_enum(enum_type, bytes, registry = {})
205
+ index = bytes[0]
206
+ raise IndexOutOfRangeError, "type: #{enum_type}" if index > enum_type[:_enum].length - 1
207
+
208
+ remaining_bytes = bytes[1..]
209
+ if enum_type[:_enum].instance_of?(Hash)
210
+ key = enum_type[:_enum].keys[index]
211
+ type = enum_type[:_enum].values[index]
212
+
213
+ value, remaining_bytes = decode(type, remaining_bytes, registry)
214
+ [
215
+ { key => value },
216
+ remaining_bytes
217
+ ]
218
+ elsif enum_type[:_enum].instance_of?(Array)
219
+ value = enum_type[:_enum][index]
220
+ debug 'value', value.inspect
221
+ [
222
+ value,
223
+ remaining_bytes
224
+ ]
225
+ end
226
+ end
227
+
228
+ def decode_struct(struct, bytes, registry = {})
229
+ values, remaining_bytes = _decode_types(struct.values, bytes, registry)
230
+ [
231
+ [struct.keys, values].transpose.to_h,
232
+ remaining_bytes
233
+ ]
234
+ end
235
+ end
236
+
237
+ def self.get_final_type_from_registry(registry, type)
238
+ mapped_type = registry[type]
239
+ if mapped_type.nil?
240
+ nil
241
+ elsif registry[mapped_type].nil?
242
+ mapped_type
243
+ else
244
+ get_final_type_from_registry(registry, mapped_type)
245
+ end
246
+ end
247
+
248
+ def self._decode_types(type_list, bytes, registry = {})
249
+ if type_list.empty?
250
+ [[], bytes]
251
+ else
252
+ value, remaining_bytes = decode(type_list.first, bytes, registry)
253
+ value_list, remaining_bytes = _decode_types(type_list[1..], remaining_bytes, registry)
254
+ [[value] + value_list, remaining_bytes]
255
+ end
256
+ end
257
+
258
+ def self.do_decode_compact(bytes)
259
+ case bytes[0] & 3
260
+ when 0
261
+ [bytes[0] >> 2, bytes[1..]]
262
+ when 1
263
+ [bytes[0..1].flip.to_uint >> 2, bytes[2..]]
264
+ when 2
265
+ [bytes[0..3].flip.to_uint >> 2, bytes[4..]]
266
+ when 3
267
+ length = 4 + (bytes[0] >> 2)
268
+ [bytes[1..length].flip.to_uint, bytes[length + 1..]]
269
+ else
270
+ raise Unreachable, 'type: Compact'
271
+ end
272
+ end
273
+
274
+ class << self
275
+ def encode(type, value, registry = {})
276
+ logger.debug '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'
277
+ logger.debug " type: #{type}"
278
+ logger.debug " value: #{value}"
279
+
280
+ bytes = do_encode(type, value, registry)
281
+
282
+ logger.debug " encoded: #{bytes}"
283
+ bytes
284
+ end
285
+
286
+ def do_encode(type, value, registry = {})
287
+ if type.instance_of?(String)
288
+ return encode_bytes(value) if bytes?(type)
289
+ return encode_boolean(value) if boolean?(type)
290
+ return encode_string(value) if string?(type)
291
+ return encode_compact(value) if compact?(type)
292
+ return encode_uint(type, value) if uint?(type)
293
+ return encode_option(type, value, registry) if option?(type)
294
+ return encode_array(type, value, registry) if array?(type)
295
+ return encode_vec(type, value, registry) if vec?(type)
296
+ return encode_tuple(type, value, registry) if tuple?(type)
297
+
298
+ registry_type = get_final_type_from_registry(registry, type)
299
+ return do_encode(registry_type, value, registry) if registry_type
300
+ elsif type.instance_of?(Hash)
301
+ return encode_enum(type, value, registry) if enum?(type)
302
+ return encode_struct(type, value, registry) if struct?(type)
303
+ end
304
+
305
+ raise NotImplemented, "type: #{type}, value: #{value.inspect}"
306
+ end
307
+
308
+ def encode_bytes(value)
309
+ encode_compact(value.length) + value
310
+ end
311
+
312
+ def encode_boolean(value)
313
+ return [0x00] if value == false
314
+ return [0x01] if value == true
315
+
316
+ raise InvalidValueError, "type: Boolean, value: #{value.inspect}"
317
+ end
318
+
319
+ def encode_string(string)
320
+ body = string.unpack('C*')
321
+ prefix = encode_compact(body.length)
322
+ prefix + body
323
+ end
324
+
325
+ def encode_compact(value)
326
+ return [value << 2] if (value >= 0) && (value < 64)
327
+ return ((value << 2) + 1).to_bytes.flip if value < 2**14
328
+ return ((value << 2) + 2).to_bytes.flip if value < 2**30
329
+
330
+ bytes = value.to_bytes.flip
331
+ [(((bytes.length - 4) << 2) + 3)] + bytes
332
+ end
333
+
334
+ def encode_uint(type, value)
335
+ bit_length = type[1..].to_i
336
+ value.to_bytes(bit_length).flip
337
+ end
338
+
339
+ def encode_option(type, value, registry = {})
340
+ return [0x00] if value.nil?
341
+
342
+ inner_type = type.scan(/\A[O|o]ption<(.+)>\z/).first.first
343
+ [0x01] + do_encode(inner_type, value, registry)
344
+ end
345
+
346
+ def encode_array(type, array, registry = {})
347
+ inner_type, length = parse_fixed_array(type)
348
+ raise LengthNotEqualErr, "type: #{type}, value: #{array.inspect}" if length != array.length
349
+
350
+ _encode_types([inner_type] * length, array, registry)
351
+ end
352
+
353
+ def encode_vec(type, array, registry = {})
354
+ inner_type = type.scan(/\A[V|v]ec<(.+)>\z/).first.first
355
+ length_bytes = encode_compact(array.length)
356
+ length_bytes + _encode_types([inner_type] * array.length, array, registry)
357
+ end
358
+
359
+ def encode_tuple(tuple_type, tuple, registry = {})
360
+ inner_types = tuple_type.scan(/\A\(\s*(.+)\s*\)\z/)[0][0].split(',').map(&:strip)
361
+ _encode_types(inner_types, tuple, registry)
362
+ end
363
+
364
+ def encode_enum(enum_type, enum, registry = {})
365
+ key = enum.keys.first
366
+ value = enum.values.first
367
+ value_type = enum_type[:_enum][key]
368
+ index = enum_type[:_enum].keys.index(key)
369
+ encode_uint('u8', index) + do_encode(value_type, value, registry)
370
+ end
371
+
372
+ def encode_struct(struct_type, struct, registry = {})
373
+ _encode_types(struct_type.values, struct.values, registry)
374
+ end
375
+
376
+ def _encode_types(type_list, value_list, registry = {})
377
+ raise LengthNotEqualErr, "type: #{type_list}, value: #{value_list.inspect}" if type_list.length != value_list.length
378
+
379
+ if type_list.empty?
380
+ []
381
+ else
382
+ bytes = do_encode(type_list.first, value_list.first, registry)
383
+ bytes + _encode_types(type_list[1..], value_list[1..], registry)
384
+ end
385
+ end
386
+ end
387
+ end
data/lib/hasher.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xxhash'
4
+ require 'blake2b'
5
+
6
+ module Hasher
7
+ class << self
8
+ # params:
9
+ # hasher: 'Identity' | 'Twox64Concat' | 'Blake2128Concat'
10
+ # bytes: u8 array
11
+ # return: u8 array
12
+ def apply_hasher(hasher, bytes)
13
+ function_name = hasher.gsub('_', '').underscore
14
+ Hasher.send(function_name, bytes)
15
+ end
16
+ end
17
+
18
+ class << self
19
+ def identity(bytes)
20
+ bytes
21
+ end
22
+
23
+ def twox64_concat(bytes)
24
+ data = bytes.to_utf8
25
+ twox64(data) + bytes
26
+ end
27
+
28
+ def blake2128_concat(bytes)
29
+ blake2_128(bytes) + bytes
30
+ end
31
+
32
+ def twox64(str)
33
+ result = XXhash.xxh64 str, 0
34
+ result.to_bytes.reverse
35
+ end
36
+
37
+ def twox128(str)
38
+ bytes = []
39
+ 2.times do |i|
40
+ result = XXhash.xxh64 str, i
41
+ bytes += result.to_bytes.reverse
42
+ end
43
+ bytes
44
+ end
45
+
46
+ def blake2_128(bytes)
47
+ Blake2b.hex(bytes, 16).to_bytes
48
+ end
49
+
50
+ def blake2_256(bytes)
51
+ Blake2b.hex(bytes, 32).to_bytes
52
+ end
53
+ end
54
+ end
data/lib/metadata.rb ADDED
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metadata
4
+ class << self
5
+ def decode_metadata(bytes)
6
+ metadata, = ScaleRb.decode('MetadataTop', bytes, TYPES)
7
+ metadata
8
+ end
9
+
10
+ def build_registry(metadata)
11
+ raise ScaleRb::NotImplemented, metadata._get(:metadata).keys.first unless metadata._get(:metadata)._key?(:v14)
12
+
13
+ metadata_v14 = metadata._get(:metadata)._get(:v14)
14
+ MetadataV14.build_registry(metadata_v14)
15
+ end
16
+
17
+ def get_storage_item(pallet_name, item_name, metadata)
18
+ raise ScaleRb::NotImplemented, metadata._get(:metadata).keys.first unless metadata._get(:metadata)._key?(:v14)
19
+
20
+ metadata_v14 = metadata._get(:metadata)._get(:v14)
21
+ MetadataV14.get_storage_item(pallet_name, item_name, metadata_v14)
22
+ end
23
+ end
24
+
25
+ TYPES = {
26
+ 'MetadataTop' => {
27
+ magic_number: 'U32',
28
+ metadata: 'Metadata'
29
+ },
30
+ 'Metadata' => {
31
+ _enum: {
32
+ v0: 'MetadataV0',
33
+ v1: 'MetadataV1',
34
+ v2: 'MetadataV2',
35
+ v3: 'MetadataV3',
36
+ v4: 'MetadataV4',
37
+ v5: 'MetadataV5',
38
+ v6: 'MetadataV6',
39
+ v7: 'MetadataV7',
40
+ v8: 'MetadataV8',
41
+ v9: 'MetadataV9',
42
+ v10: 'MetadataV10',
43
+ v11: 'MetadataV11',
44
+ v12: 'MetadataV12',
45
+ v13: 'MetadataV13',
46
+ v14: 'MetadataV14'
47
+ }
48
+ },
49
+
50
+ 'MetadataV14' => {
51
+ lookup: 'PortableRegistry',
52
+ pallets: 'Vec<PalletMetadataV14>',
53
+ extrinsic: 'ExtrinsicMetadataV14',
54
+ type: 'SiLookupTypeId'
55
+ },
56
+
57
+ # PortableRegistry begin
58
+ 'PortableRegistry' => {
59
+ types: 'Vec<PortableTypeV14>'
60
+ },
61
+ 'PortableTypeV14' => {
62
+ id: 'Si1LookupTypeId',
63
+ type: 'Si1Type'
64
+ },
65
+ 'Si1LookupTypeId' => 'Compact',
66
+ 'Si1Type' => {
67
+ path: 'Si1Path',
68
+ params: 'Vec<Si1TypeParameter>',
69
+ def: 'Si1TypeDef',
70
+ docs: 'Vec<Text>'
71
+ },
72
+ 'Si1Path' => 'Vec<Text>',
73
+ 'Si1TypeParameter' => {
74
+ name: 'Text',
75
+ type: 'Option<Si1LookupTypeId>'
76
+ },
77
+ 'Si1TypeDef' => {
78
+ _enum: {
79
+ composite: 'Si1TypeDefComposite',
80
+ variant: 'Si1TypeDefVariant',
81
+ sequence: 'Si1TypeDefSequence',
82
+ array: 'Si1TypeDefArray',
83
+ tuple: 'Si1TypeDefTuple',
84
+ primitive: 'Si1TypeDefPrimitive',
85
+ compact: 'Si1TypeDefCompact',
86
+ bitSequence: 'Si1TypeDefBitSequence',
87
+ historicMetaCompat: 'Text' # TODO: sanitize?
88
+ }
89
+ },
90
+ 'Si1TypeDefComposite' => {
91
+ fields: 'Vec<Si1Field>'
92
+ },
93
+ 'Si1Field' => {
94
+ name: 'Option<Text>',
95
+ type: 'Si1LookupTypeId',
96
+ typeName: 'Option<Text>',
97
+ docs: 'Vec<Text>'
98
+ },
99
+ 'Si1TypeDefVariant' => {
100
+ variants: 'Vec<Si1Variant>'
101
+ },
102
+ 'Si1Variant' => {
103
+ name: 'Text',
104
+ fields: 'Vec<Si1Field>',
105
+ index: 'u8',
106
+ docs: 'Vec<Text>'
107
+ },
108
+ 'Si1TypeDefSequence' => {
109
+ type: 'Si1LookupTypeId'
110
+ },
111
+ 'Si1TypeDefArray' => {
112
+ len: 'u32',
113
+ type: 'Si1LookupTypeId'
114
+ },
115
+ 'Si1TypeDefTuple' => 'Vec<Si1LookupTypeId>',
116
+ 'Si1TypeDefPrimitive' => {
117
+ _enum: %w[
118
+ Bool Char Str U8 U16 U32 U64 U128 U256 I8 I16 I32 I64 I128 I256
119
+ ]
120
+ },
121
+ 'Si1TypeDefCompact' => {
122
+ type: 'Si1LookupTypeId'
123
+ },
124
+ 'Si1TypeDefBitSequence' => {
125
+ bitStoreType: 'Si1LookupTypeId',
126
+ bitOrderType: 'Si1LookupTypeId'
127
+ },
128
+ # PortableRegistry end
129
+
130
+ # PalletMetadataV14 begin
131
+ 'PalletMetadataV14' => {
132
+ name: 'Text',
133
+ storage: 'Option<PalletStorageMetadataV14>',
134
+ calls: 'Option<PalletCallMetadataV14>',
135
+ events: 'Option<PalletEventMetadataV14>',
136
+ constants: 'Vec<PalletConstantMetadataV14>',
137
+ errors: 'Option<PalletErrorMetadataV14>',
138
+ index: 'U8'
139
+ },
140
+ 'PalletStorageMetadataV14' => {
141
+ prefix: 'Text',
142
+ items: 'Vec<StorageEntryMetadataV14>'
143
+ },
144
+ 'StorageEntryMetadataV14' => {
145
+ name: 'Text',
146
+ modifier: 'StorageEntryModifierV14',
147
+ type: 'StorageEntryTypeV14',
148
+ fallback: 'Bytes',
149
+ docs: 'Vec<Text>'
150
+ },
151
+ 'StorageEntryModifierV14' => {
152
+ _enum: %w[Optional Default Required]
153
+ },
154
+ 'StorageEntryTypeV14' => {
155
+ _enum: {
156
+ plain: 'SiLookupTypeId',
157
+ map: {
158
+ hashers: 'Vec<StorageHasherV14>',
159
+ key: 'SiLookupTypeId',
160
+ value: 'SiLookupTypeId'
161
+ }
162
+ }
163
+ },
164
+ 'StorageHasherV14' => {
165
+ _enum: %w[Blake2128 Blake2256 Blake2128Concat Twox128 Twox256 Twox64Concat Identity]
166
+ },
167
+ 'PalletCallMetadataV14' => {
168
+ type: 'Si1LookupTypeId'
169
+ },
170
+ 'PalletEventMetadataV14' => {
171
+ type: 'SiLookupTypeId'
172
+ },
173
+ 'PalletConstantMetadataV14' => {
174
+ name: 'Text',
175
+ type: 'SiLookupTypeId',
176
+ value: 'Bytes',
177
+ docs: 'Vec<Text>'
178
+ },
179
+ 'PalletErrorMetadataV14' => {
180
+ type: 'SiLookupTypeId'
181
+ },
182
+ # PalletMetadataV14 end
183
+
184
+ # ExtrinsicMetadataV14 begin
185
+ 'ExtrinsicMetadataV14' => {
186
+ type: 'SiLookupTypeId',
187
+ version: 'u8',
188
+ signedExtensions: 'Vec<SignedExtensionMetadataV14>'
189
+ },
190
+ 'SignedExtensionMetadataV14' => {
191
+ identifier: 'Text',
192
+ type: 'SiLookupTypeId',
193
+ additionalSigned: 'SiLookupTypeId'
194
+ },
195
+ # ExtrinsicMetadataV14 end
196
+
197
+ 'SiLookupTypeId' => 'Compact'
198
+ }.freeze
199
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MetadataV14
4
+ class << self
5
+ def build_registry(metadata)
6
+ types = metadata._get(:lookup)._get(:types)
7
+ types.map { |type| [type._get(:id), type._get(:type)] }.to_h
8
+ end
9
+
10
+ def get_storage_item(pallet_name, item_name, metadata)
11
+ pallet =
12
+ metadata._get(:pallets).find do |p|
13
+ p._get(:name) == pallet_name
14
+ end
15
+
16
+ pallet._get(:storage)._get(:items).find do |item|
17
+ item._get(:name) == item_name
18
+ end
19
+ end
20
+ end
21
+ end