scale.rb 0.2.17 → 0.3.1

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Cargo.lock +8 -4
  4. data/Cargo.toml +2 -3
  5. data/Dockerfile +4 -1
  6. data/Gemfile.lock +23 -21
  7. data/README.md +44 -1
  8. data/Rakefile +6 -0
  9. data/exe/scale +49 -0
  10. data/lib/helper.rb +19 -4
  11. data/lib/metadata/metadata.rb +27 -17
  12. data/lib/metadata/metadata_v0.rb +24 -20
  13. data/lib/metadata/metadata_v1.rb +13 -9
  14. data/lib/metadata/metadata_v10.rb +2 -2
  15. data/lib/metadata/metadata_v11.rb +2 -2
  16. data/lib/metadata/metadata_v12.rb +9 -8
  17. data/lib/metadata/metadata_v13.rb +161 -0
  18. data/lib/metadata/metadata_v2.rb +2 -2
  19. data/lib/metadata/metadata_v3.rb +2 -2
  20. data/lib/metadata/metadata_v4.rb +21 -11
  21. data/lib/metadata/metadata_v5.rb +21 -11
  22. data/lib/metadata/metadata_v6.rb +9 -9
  23. data/lib/metadata/metadata_v7.rb +26 -15
  24. data/lib/metadata/metadata_v8.rb +9 -9
  25. data/lib/metadata/metadata_v9.rb +2 -2
  26. data/lib/scale.rb +40 -339
  27. data/lib/scale/base.rb +175 -93
  28. data/lib/scale/block.rb +10 -10
  29. data/lib/scale/trie.rb +1 -1
  30. data/lib/scale/types.rb +139 -40
  31. data/lib/scale/version.rb +1 -1
  32. data/lib/scale_bytes.rb +63 -0
  33. data/lib/substrate_client.rb +11 -8
  34. data/lib/type_builder.rb +280 -0
  35. data/lib/type_registry.rb +91 -0
  36. data/lib/type_registry/crab.json +676 -595
  37. data/lib/type_registry/darwinia.json +730 -554
  38. data/lib/type_registry/default.json +3 -2
  39. data/lib/type_registry/pangolin.json +771 -0
  40. data/scale.gemspec +1 -0
  41. data/scripts/mmr_root_to_sign.rb +10 -0
  42. data/src/lib.rs +80 -25
  43. metadata +25 -10
  44. data/lib/type_registry/edgeware.json +0 -124
  45. data/lib/type_registry/joystream.json +0 -49
  46. data/lib/type_registry/kulupu.json +0 -15
  47. data/lib/type_registry/plasm.json +0 -89
  48. data/lib/type_registry/robonomics.json +0 -39
  49. data/lib/type_registry/westend.json +0 -63
  50. data/src/storage_key.rs +0 -41
data/lib/scale/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Scale
2
- VERSION = "0.2.17".freeze
2
+ VERSION = "0.3.1".freeze
3
3
  end
@@ -0,0 +1,63 @@
1
+ module Scale
2
+ class Bytes
3
+ attr_reader :data, :bytes
4
+ attr_reader :offset
5
+
6
+ def initialize(data)
7
+ if (data.class == Array) && data.is_byte_array?
8
+ @bytes = data
9
+ elsif (data.class == String) && data.start_with?("0x") && (data.length % 2 == 0)
10
+ arr = data[2..].scan(/../).map(&:hex)
11
+ @bytes = arr
12
+ else
13
+ raise "Provided data is not valid"
14
+ end
15
+
16
+ @data = data
17
+ @offset = 0
18
+ end
19
+
20
+ def reset_offset
21
+ @offset = 0
22
+ end
23
+
24
+ def get_next_bytes(length)
25
+ result = @bytes[@offset...@offset + length]
26
+ if result.length < length
27
+ str = @data[(2 + @offset * 2)..]
28
+ str = str.length > 40 ? (str[0...40]).to_s + "..." : str
29
+ raise "No enough data: #{str}, expect length: #{length}, but #{result.length}"
30
+ end
31
+ @offset += length
32
+ result
33
+ rescue RangeError => ex
34
+ puts "length: #{length}"
35
+ puts ex.message
36
+ puts ex.backtrace
37
+ end
38
+
39
+ def get_remaining_bytes
40
+ @bytes[offset..]
41
+ end
42
+
43
+ def to_hex_string
44
+ @bytes.bytes_to_hex
45
+ end
46
+
47
+ def to_bin_string
48
+ @bytes.bytes_to_bin
49
+ end
50
+
51
+ def to_ascii
52
+ @bytes[0...offset].pack("C*") + "<================================>" + @bytes[offset..].pack("C*")
53
+ end
54
+
55
+ def ==(other)
56
+ bytes == other.bytes && offset == other.offset
57
+ end
58
+
59
+ def to_s
60
+ green(@bytes[0...offset].bytes_to_hex) + yellow(@bytes[offset..].bytes_to_hex[2..])
61
+ end
62
+ end
63
+ end
@@ -39,6 +39,7 @@ class SubstrateClient
39
39
  @url = url
40
40
  @request_id = 1
41
41
  @metadata_cache = {}
42
+ init_types_and_metadata
42
43
  end
43
44
 
44
45
  def request(method, params)
@@ -121,8 +122,8 @@ class SubstrateClient
121
122
  block = self.chain_getBlock(block_hash)
122
123
  SubstrateClient::Helper.decode_block(block)
123
124
  rescue => ex
124
- puts ex.message
125
- puts ex.backtrace.join("\n\t")
125
+ Scale::Types.logger.error ex
126
+ Scale::Types.logger.error ex.backtrace.join("\n\t")
126
127
  end
127
128
 
128
129
  def get_block_events(block_hash=nil)
@@ -136,16 +137,18 @@ class SubstrateClient
136
137
  [events_data, decoded]
137
138
  end
138
139
 
139
- # Plain: client.get_storage("Sudo", "Key")
140
- # Plain: client.get_storage("Balances", "TotalIssuance")
141
- # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
142
- # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
143
140
  def get_storage(module_name, storage_name, params = nil, block_hash = nil)
144
141
  self.init_types_and_metadata(block_hash)
145
142
 
146
- storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
143
+ storage_key, return_type, storage_item = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
144
+
147
145
  data = self.state_getStorage(storage_key, block_hash)
148
- return unless data
146
+
147
+ if data.nil?
148
+ return if storage_item[:modifier] == "Optional"
149
+
150
+ data = storage_item[:fallback]
151
+ end
149
152
 
150
153
  bytes = Scale::Bytes.new(data)
151
154
  type = Scale::Types.get(return_type)
@@ -0,0 +1,280 @@
1
+ module Scale
2
+ module Types
3
+ class << self
4
+
5
+ # type_info: type_string or type_def
6
+ # type_string: hard coded type name, Compact, H128, Vec<Compact>, (U32, U128), ...
7
+ # type_def : struct, enum, set
8
+ #
9
+ # if type_string start_with Scale::Types::, it is treat as a hard coded type
10
+ def get(type_info)
11
+ if type_info.class == ::String
12
+ if type_info.start_with?('Scale::Types::')
13
+ return get_hard_coded_type(type_info)
14
+ end
15
+
16
+ # find the final type from registry
17
+ type_info = fix_name(type_info)
18
+ type_info = get_final_type_from_registry(type_info)
19
+ end
20
+
21
+ build_type(type_info)
22
+ end
23
+
24
+ private
25
+ def build_type(type_info)
26
+ # 1. hard coded types, 2. Vec<...>, 3. Option<...>, 4. [xx; x], 5. (x, y)
27
+ if type_info.class == ::String
28
+ type_string = fix_name(type_info)
29
+ if type_string =~ /\AVec<.+>\z/
30
+ build_vec(type_string)
31
+ elsif type_info =~ /\AOption<.+>\z/
32
+ build_option(type_string)
33
+ elsif type_info =~ /\A\[.+;\s*\d+\]\z/
34
+ build_array(type_string)
35
+ elsif type_info =~ /\A\(.+\)\z/
36
+ build_tuple(type_string)
37
+ else
38
+ get_hard_coded_type(type_string)
39
+ end
40
+
41
+ # 5. Struct, 6. Enum, 7. Set
42
+ else
43
+
44
+ type_info.transform_keys!(&:to_sym)
45
+ if type_info[:type] == "struct"
46
+ build_struct(type_info)
47
+ elsif type_info[:type] == "enum"
48
+ build_enum(type_info)
49
+ elsif type_info[:type] == "set"
50
+ build_set(type_info)
51
+ else
52
+ raise Scale::TypeBuildError.new("Failed to build a type from #{type_info}")
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+ def get_final_type_from_registry(type_info)
59
+ type_registry = TypeRegistry.instance
60
+ if type_registry.types.nil?
61
+ raise TypeRegistryNotLoadYet
62
+ end
63
+ TypeRegistry.instance.get(type_info)
64
+ end
65
+
66
+ def get_hard_coded_type(type_name)
67
+ # type_name = rename(type_name)
68
+ type_name = (type_name.start_with?("Scale::Types::") ? type_name : "Scale::Types::#{type_name}")
69
+ type_name.constantize2
70
+ rescue => e
71
+ raise Scale::TypeBuildError.new("Failed to get the hard coded type named `#{type_name}`")
72
+ end
73
+
74
+ def build_vec(type_string)
75
+ inner_type_str = type_string.scan(/\AVec<(.+)>\z/).first.first
76
+ inner_type = get(inner_type_str)
77
+
78
+ type_name = "Vec_#{inner_type.name.gsub('Scale::Types::', '')}_"
79
+
80
+ if !Scale::Types.const_defined?(type_name)
81
+ klass = Class.new do
82
+ include Scale::Types::Vec
83
+ inner_type inner_type
84
+ end
85
+ Scale::Types.const_set type_name, klass
86
+ else
87
+ Scale::Types.const_get type_name
88
+ end
89
+ end
90
+
91
+ def build_option(type_string)
92
+ inner_type_str = type_string.scan(/\AOption<(.+)>\z/).first.first
93
+
94
+ # an exception
95
+ # https://substrate.dev/docs/en/knowledgebase/advanced/codec#options
96
+ return get("Scale::Types::OptionBool") if inner_type_str.camelize2 == "Bool"
97
+
98
+ inner_type = get(inner_type_str)
99
+
100
+ type_name = "Option_#{inner_type.name.gsub('Scale::Types::', '')}_"
101
+
102
+ if !Scale::Types.const_defined?(type_name)
103
+ klass = Class.new do
104
+ include Scale::Types::Option
105
+ inner_type inner_type
106
+ end
107
+ Scale::Types.const_set type_name, klass
108
+ else
109
+ Scale::Types.const_get type_name
110
+ end
111
+ end
112
+
113
+ def build_array(type_string)
114
+ scan_result = type_string.scan /\[(.+);\s*(\d+)\]/
115
+
116
+ #
117
+ inner_type_str = scan_result[0][0]
118
+ inner_type = get(inner_type_str)
119
+
120
+ #
121
+ len = scan_result[0][1].to_i
122
+
123
+ type_name = "Array_#{inner_type.name.gsub('Scale::Types::', '')}_#{len}_"
124
+
125
+ if !Scale::Types.const_defined?(type_name)
126
+ klass = Class.new do
127
+ include Scale::Types::Array
128
+ inner_type inner_type
129
+ length len
130
+ end
131
+ Scale::Types.const_set type_name, klass
132
+ else
133
+ Scale::Types.const_get type_name
134
+ end
135
+ end
136
+
137
+ def build_tuple(type_string)
138
+ scan_result = type_string.scan /\A\((.+)\)\z/
139
+ inner_types_str = scan_result[0][0]
140
+ inner_type_strs = inner_types_str.split(",").map do |inner_type_str|
141
+ inner_type_str.strip
142
+ end
143
+
144
+ inner_types = inner_type_strs.map do |inner_type_str|
145
+ get(inner_type_str)
146
+ end
147
+
148
+ type_name = "Tuple_#{inner_types.map {|inner_type| inner_type.name.gsub('Scale::Types::', '')}.join("_")}_"
149
+ if !Scale::Types.const_defined?(type_name)
150
+ klass = Class.new do
151
+ include Scale::Types::Tuple
152
+ inner_types(*inner_types)
153
+ end
154
+ Scale::Types.const_set type_name, klass
155
+ else
156
+ Scale::Types.const_get type_name
157
+ end
158
+ end
159
+
160
+ def build_struct(type_info)
161
+ # items: {"a" => Type}
162
+ items = type_info[:type_mapping].map do |item|
163
+ item_name = item[0]
164
+ item_type = get(item[1])
165
+ [item_name, item_type]
166
+ end.to_h
167
+
168
+ partials = []
169
+ items.each_pair do |item_name, item_type|
170
+ partials << item_name.camelize2 + 'In' + item_type.name.gsub('Scale::Types::', '')
171
+ end
172
+ type_name = "Struct_#{partials.join('_')}_"
173
+
174
+ if !Scale::Types.const_defined?(type_name)
175
+ klass = Class.new do
176
+ include Scale::Types::Struct
177
+ items(**items)
178
+ end
179
+ Scale::Types.const_set type_name, klass
180
+ else
181
+ Scale::Types.const_get type_name
182
+ end
183
+ end
184
+
185
+ # not implemented: ["Compact", "Hex"]
186
+ def build_enum(type_info)
187
+ # type_mapping: [["Item1", "Compact"], [["Item2", "Hex"]]
188
+ if type_info.has_key?(:type_mapping)
189
+ # items: {a: Type}
190
+ items = type_info[:type_mapping].map do |item|
191
+ item_name = item[0]
192
+ item_type = get(item[1])
193
+ [item_name.to_sym, item_type]
194
+ end.to_h
195
+
196
+ partials = []
197
+ items.each_pair do |item_name, item_type|
198
+ partials << item_name.to_s.camelize2 + 'In' + item_type.name.gsub('Scale::Types::', '')
199
+ end
200
+ type_name = "Enum_#{partials.join('_')}_"
201
+
202
+ if !Scale::Types.const_defined?(type_name)
203
+ klass = Class.new do
204
+ include Scale::Types::Enum
205
+ items(**items)
206
+ end
207
+
208
+ return Scale::Types.const_set type_name, klass
209
+ else
210
+ return Scale::Types.const_get type_name
211
+ end
212
+ end
213
+
214
+ # value_list: [1, "hello"]
215
+ if type_info.has_key?(:value_list)
216
+ type_name = "Enum#{type_info[:value_list].map {|value| value.to_s.camelize2}.join}"
217
+
218
+ if !Scale::Types.const_defined?(type_name)
219
+ klass = Class.new do
220
+ include Scale::Types::Enum
221
+ values *type_info[:value_list]
222
+ end
223
+ return Scale::Types.const_set type_name, klass
224
+ else
225
+ return Scale::Types.const_get type_name
226
+ end
227
+ end
228
+ end
229
+
230
+
231
+ # {
232
+ # value_type: u32,
233
+ # value_list: {
234
+ # "TransactionPayment" => 0b00000001,
235
+ # "Transfer" => 0b00000010,
236
+ # "Reserve" => 0b00000100,
237
+ # ...
238
+ # }
239
+ # }
240
+ def build_set(type_info)
241
+ type_name = "Set#{type_info[:value_list].keys.map(&:camelize2).join("")}"
242
+ if !Scale::Types.const_defined?(type_name)
243
+ bytes_length = type_info[:value_type][1..].to_i / 8
244
+ klass = Class.new do
245
+ include Scale::Types::Set
246
+ items type_info[:value_list], bytes_length
247
+ end
248
+ return Scale::Types.const_set type_name, klass
249
+ else
250
+ return Scale::Types.const_get type_name
251
+ end
252
+ Scale::Types.const_set fix(name), klass
253
+ end
254
+
255
+ def fix_name(type)
256
+ type = type.gsub("T::", "")
257
+ .gsub("<T>", "")
258
+ .gsub("<T as Trait>::", "")
259
+ .delete("\n")
260
+ .gsub(/(u)(\d+)/, 'U\2')
261
+ return "Bool" if type == "bool"
262
+ return "Null" if type == "()"
263
+ return "String" if type == "Vec<u8>"
264
+ return "Compact" if type == "Compact<u32>" || type == "Compact<U32>"
265
+ return "Address" if type == "<Lookup as StaticLookup>::Source"
266
+ return "Compact" if type == "<Balance as HasCompact>::Type"
267
+ return "Compact" if type == "<BlockNumber as HasCompact>::Type"
268
+ return "Compact" if type =~ /\ACompact<[a-zA-Z0-9\s]*>\z/
269
+ return "CompactMoment" if type == "<Moment as HasCompact>::Type"
270
+ return "CompactMoment" if type == "Compact<Moment>"
271
+ return "InherentOfflineReport" if type == "<InherentOfflineReport as InherentOfflineReport>::Inherent"
272
+ return "AccountData" if type == "AccountData<Balance>"
273
+ return "EventRecord" if type == "EventRecord<Event, Hash>"
274
+
275
+ type
276
+ end
277
+
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,91 @@
1
+ module Scale
2
+
3
+ class TypeRegistry
4
+ include Singleton
5
+
6
+ # init by load, and will not change
7
+ attr_reader :spec_name, :types
8
+ attr_reader :versioning, :custom_types # optional
9
+
10
+ # will change by different spec version
11
+ attr_accessor :spec_version # optional
12
+ attr_accessor :metadata
13
+
14
+ def load(spec_name: nil, custom_types: nil)
15
+ @spec_name = nil
16
+ @types = nil
17
+ @versioning = nil
18
+ @custom_types = nil
19
+
20
+ default_types, _, _ = load_chain_spec_types("default")
21
+
22
+ if spec_name
23
+ begin
24
+ @spec_name = spec_name
25
+ spec_types, @versioning, @spec_version = load_chain_spec_types(spec_name)
26
+ @types = default_types.merge(spec_types)
27
+ rescue => ex
28
+ # TODO: check different errors
29
+ Scale::Types.logger.error "There is no types json file named #{spec_name}"
30
+ @types = default_types
31
+ end
32
+ else
33
+ @spec_name = "default"
34
+ @types = default_types
35
+ end
36
+
37
+ self.custom_types = custom_types
38
+ true
39
+ end
40
+
41
+ def get(type_name)
42
+ all_types = self.all_types
43
+ type_traverse(type_name, all_types)
44
+ end
45
+
46
+ def custom_types=(custom_types)
47
+ @custom_types = custom_types.stringify_keys if (not custom_types.nil?) && custom_types.class.name == "Hash"
48
+ end
49
+
50
+ def all_types
51
+ all_types = {}.merge(@types)
52
+
53
+ if @spec_version && @versioning
54
+ @versioning.each do |item|
55
+ if @spec_version >= item["runtime_range"][0] &&
56
+ ( item["runtime_range"][1].nil? || @spec_version <= item["runtime_range"][1] )
57
+ all_types.merge!(item["types"])
58
+ end
59
+ end
60
+ end
61
+
62
+ all_types.merge!(@custom_types) if @custom_types
63
+ all_types
64
+ end
65
+
66
+ private
67
+
68
+ def load_chain_spec_types(spec_name)
69
+ file = File.join File.expand_path("../..", __FILE__), "lib", "type_registry", "#{spec_name}.json"
70
+ json_string = File.open(file).read
71
+ json = JSON.parse(json_string)
72
+
73
+ runtime_id = json["runtime_id"]
74
+
75
+ [json["types"], json["versioning"], runtime_id]
76
+ end
77
+
78
+ def type_traverse(type, types)
79
+ if type.class == ::String
80
+ real_type = types[type]
81
+
82
+ return type if real_type.nil? || real_type == type
83
+
84
+ type_traverse(real_type, types)
85
+ else
86
+ type
87
+ end
88
+ end
89
+ end
90
+
91
+ end