scale_rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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