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.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://www.rubyguides.com/2017/01/read-binary-data/
4
+ class String
5
+ def to_bytes
6
+ data = start_with?('0x') ? self[2..] : self
7
+ raise 'Not valid hex string' if data =~ /[^\da-f]+/i
8
+
9
+ data = "0#{data}" if data.length.odd?
10
+ data.scan(/../).map(&:hex)
11
+ end
12
+
13
+ def to_camel
14
+ split('_').collect(&:capitalize).join
15
+ end
16
+
17
+ def underscore
18
+ gsub(/::/, '/')
19
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
20
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
21
+ .tr('-', '_')
22
+ .downcase
23
+ end
24
+ end
25
+
26
+ class Integer
27
+ def to_bytes(bit_length = nil)
28
+ return to_s(16).to_bytes unless bit_length
29
+
30
+ hex = to_s(16).rjust(bit_length / 4, '0')
31
+ hex.to_bytes
32
+ end
33
+
34
+ # unsigned to signed
35
+ def to_signed(bit_length)
36
+ unsigned_mid = 2**(bit_length - 1)
37
+ unsigned_ceiling = 2**bit_length
38
+ self >= unsigned_mid ? self - unsigned_ceiling : self
39
+ end
40
+
41
+ # signed to unsigned
42
+ def to_unsigned(bit_length)
43
+ unsigned_mid = 2**(bit_length - 1)
44
+ unsigned_ceiling = 2**bit_length
45
+ raise 'out of scope' if self >= unsigned_mid || self <= -unsigned_mid
46
+
47
+ negative? ? unsigned_ceiling + self : self
48
+ end
49
+
50
+ # unix timestamp to utc
51
+ def to_utc
52
+ Time.at(self).utc.asctime
53
+ end
54
+
55
+ # utc to unix timestamp
56
+ def from_utc(utc_asctime)
57
+ Time.parse(utc_asctime)
58
+ end
59
+ end
60
+
61
+ class Array
62
+ def to_hex
63
+ raise 'Not a byte array' unless byte_array?
64
+
65
+ reduce('0x') { |hex, byte| hex + byte.to_s(16).rjust(2, '0') }
66
+ end
67
+
68
+ def to_bin
69
+ raise 'Not a byte array' unless byte_array?
70
+
71
+ reduce('0b') { |bin, byte| bin + byte.to_s(2).rjust(8, '0') }
72
+ end
73
+
74
+ def to_utf8
75
+ raise 'Not a byte array' unless byte_array?
76
+
77
+ pack('C*').force_encoding('utf-8')
78
+ end
79
+
80
+ def to_uint
81
+ to_hex.to_i(16)
82
+ end
83
+
84
+ def to_int(bit_length)
85
+ to_uint.to_signed(bit_length)
86
+ end
87
+
88
+ def flip
89
+ reverse
90
+ end
91
+
92
+ def byte_array?
93
+ all? { |e| e >= 0 and e <= 255 }
94
+ end
95
+ end
96
+
97
+ class Hash
98
+ def _key?(key)
99
+ if key.instance_of?(String)
100
+ key?(key) || key?(key.to_sym)
101
+ else
102
+ key?(key) || key?(key.to_s)
103
+ end
104
+ end
105
+
106
+ def _get(key)
107
+ if key.instance_of?(String)
108
+ self[key] || self[key.to_sym]
109
+ else
110
+ self[key] || self[key.to_s]
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PortableCodec
4
+ class Error < StandardError; end
5
+ class TypeNotFound < Error; end
6
+ class TypeNotImplemented < Error; end
7
+ class CompositeInvalidValue < Error; end
8
+ class ArrayLengthNotEqual < Error; end
9
+ class VariantItemNotFound < Error; end
10
+ class VariantIndexOutOfRange < Error; end
11
+ class VariantInvalidValue < Error; end
12
+
13
+ class << self
14
+ # registry:
15
+ # {
16
+ # 0 => {
17
+ # path: [...],
18
+ # params: [...],
19
+ # def: {
20
+ # primitive: 'u8' | array: {} | ...
21
+ # }
22
+ # },
23
+ # 1 => {
24
+ # ...
25
+ # }
26
+ # }
27
+ def decode(id, bytes, registry)
28
+ type = registry[id]
29
+ raise TypeNotFound, "id: #{id}" if type.nil?
30
+
31
+ _path = type._get(:path)
32
+ _params = type._get(:params)
33
+ type_def = type._get(:def)
34
+
35
+ return decode_primitive(type_def, bytes) if type_def._key?(:primitive)
36
+ return decode_compact(bytes) if type_def._key?(:compact)
37
+ return decode_array(type_def._get(:array), bytes, registry) if type_def._key?(:array)
38
+ return decode_sequence(type_def._get(:sequence), bytes, registry) if type_def._key?(:sequence)
39
+ return decode_tuple(type_def._get(:tuple), bytes, registry) if type_def._key?(:tuple)
40
+ return decode_composite(type_def._get(:composite), bytes, registry) if type_def._key?(:composite)
41
+ return decode_variant(type_def._get(:variant), bytes, registry) if type_def._key?(:variant)
42
+
43
+ raise TypeNotImplemented, "id: #{id}"
44
+ end
45
+
46
+ # Uint, Str, Bool
47
+ # Int, Bytes ?
48
+ def decode_primitive(type_def, bytes)
49
+ primitive = type_def._get(:primitive)
50
+ return ScaleRb.decode_uint(primitive, bytes) if uint?(primitive)
51
+ return ScaleRb.decode_string(bytes) if string?(primitive)
52
+ return ScaleRb.decode_boolean(bytes) if boolean?(primitive)
53
+ # return ScaleRb.decode_int(primitive, bytes) if int?(primitive)
54
+ # return ScaleRb.decode_bytes(bytes) if bytes?(primitive)
55
+ end
56
+
57
+ def decode_compact(bytes)
58
+ ScaleRb.decode_compact(bytes)
59
+ end
60
+
61
+ def decode_array(array_type, bytes, registry)
62
+ len = array_type._get(:len)
63
+ inner_type_id = array_type._get(:type)
64
+ _decode_types([inner_type_id] * len, bytes, registry)
65
+ end
66
+
67
+ def decode_sequence(sequence_type, bytes, registry)
68
+ len, remaining_bytes = decode_compact(bytes)
69
+ inner_type_id = sequence_type._get(:type)
70
+ _decode_types([inner_type_id] * len, remaining_bytes, registry)
71
+ end
72
+
73
+ def decode_tuple(tuple_type, bytes, registry)
74
+ _decode_types(tuple_type, bytes, registry)
75
+ end
76
+
77
+ # {
78
+ # name: value,
79
+ # ...
80
+ # }
81
+ def decode_composite(composite_type, bytes, registry)
82
+ fields = composite_type._get(:fields)
83
+
84
+ type_name_list = fields.map { |f| f._get(:name) }
85
+ type_id_list = fields.map { |f| f._get(:type) }
86
+
87
+ type_value_list, remaining_bytes = _decode_types(type_id_list, bytes, registry)
88
+ [
89
+ if type_name_list.all?(&:nil?)
90
+ type_value_list
91
+ else
92
+ [type_name_list.map(&:to_sym), type_value_list].transpose.to_h
93
+ end,
94
+ remaining_bytes
95
+ ]
96
+ end
97
+
98
+ def decode_variant(variant_type, bytes, registry)
99
+ variants = variant_type._get(:variants)
100
+
101
+ index = bytes[0]
102
+ if index > (variants.length - 1)
103
+ raise VariantIndexOutOfRange,
104
+ "type: #{variant_type}, index: #{index}, bytes: #{bytes}"
105
+ end
106
+
107
+ item_variant = variants.find { |v| v._get(:index) == index }
108
+ item_name = item_variant._get(:name)
109
+ item, remaining_bytes = decode_composite(item_variant, bytes[1..], registry)
110
+
111
+ [
112
+ item.empty? ? item_name : { item_name.to_sym => item },
113
+ remaining_bytes
114
+ ]
115
+ end
116
+
117
+ def _decode_types(ids, bytes, registry = {})
118
+ if ids.empty?
119
+ [[], bytes]
120
+ else
121
+ value, remaining_bytes = decode(ids.first, bytes, registry)
122
+ value_list, remaining_bytes = _decode_types(ids[1..], remaining_bytes, registry)
123
+ [[value] + value_list, remaining_bytes]
124
+ end
125
+ end
126
+
127
+ def encode_with_hasher(value, type_id, registry, hasher)
128
+ value_bytes = encode(type_id, value, registry)
129
+ Hasher.apply_hasher(hasher, value_bytes)
130
+ end
131
+
132
+ def encode(id, value, registry)
133
+ type = registry[id]
134
+ raise TypeNotFound, "id: #{id}" if type.nil?
135
+
136
+ type_def = type._get(:def)
137
+
138
+ return encode_primitive(type_def, value) if type_def._key?(:primitive)
139
+ return encode_compact(value) if type_def._key?(:compact)
140
+ return encode_array(type_def._get(:array), value, registry) if type_def._key?(:array)
141
+ return encode_sequence(type_def._get(:sequence), value, registry) if type_def._key?(:sequence)
142
+ return encode_tuple(type_def._get(:tuple), value, registry) if type_def._key?(:tuple)
143
+ return encode_composite(type_def._get(:composite), value, registry) if type_def._key?(:composite)
144
+ return encode_variant(type_def._get(:variant), value, registry) if type_def._key?(:variant)
145
+
146
+ raise TypeNotImplemented, "id: #{id}"
147
+ end
148
+
149
+ def encode_primitive(type_def, value)
150
+ primitive = type_def._get(:primitive)
151
+ return ScaleRb.encode_uint(primitive, value) if uint?(primitive)
152
+ return ScaleRb.encode_string(value) if string?(primitive)
153
+ return ScaleRb.encode_boolean(value) if boolean?(primitive)
154
+ end
155
+
156
+ def encode_compact(value)
157
+ ScaleRb.encode_compact(value)
158
+ end
159
+
160
+ def encode_array(array_type, value, registry)
161
+ length = array_type._get(:len)
162
+ inner_type_id = array_type._get(:type)
163
+ raise ArrayLengthNotEqual, "type: #{array_type}, value: #{value.inspect}" if length != value.length
164
+
165
+ _encode_types([inner_type_id] * length, value, registry)
166
+ end
167
+
168
+ def encode_sequence(sequence_type, value, registry)
169
+ inner_type_id = sequence_type._get(:type)
170
+ length_bytes = encode_compact(value.length)
171
+ length_bytes + _encode_types([inner_type_id] * array.length, value, registry)
172
+ end
173
+
174
+ # tuple_type: [type_id1, type_id2, ...]
175
+ def encode_tuple(tuple_type, value, registry)
176
+ _encode_types(tuple_type, value, registry)
177
+ end
178
+
179
+ # value:
180
+ # {
181
+ # name1: value1,
182
+ # name2: value2,
183
+ # ...
184
+ # }
185
+ # or
186
+ # [value1, value2, ...]
187
+ def encode_composite(composite_type, value, registry)
188
+ values =
189
+ if value.instance_of?(Hash)
190
+ value.values
191
+ elsif value.instance_of?(Array)
192
+ value
193
+ else
194
+ raise CompositeInvalidValue, "value: #{value}, only hash and array"
195
+ end
196
+
197
+ fields = composite_type._get(:fields)
198
+ type_id_list = fields.map { |f| f._get(:type) }
199
+ _encode_types(type_id_list, values, registry)
200
+ end
201
+
202
+ # value:
203
+ # {
204
+ # name: the_value(Hash)
205
+ # }
206
+ # or
207
+ # the_value(String)
208
+ def encode_variant(variant_type, value, registry)
209
+ variants = variant_type._get(:variants)
210
+
211
+ if value.instance_of?(Hash)
212
+ name = value.keys.first.to_s
213
+ the_value = value.values.first
214
+ elsif value.instance_of?(String)
215
+ name = value
216
+ the_value = {}
217
+ else
218
+ raise VariantInvalidValue, "type: #{variant_type}, value: #{value}"
219
+ end
220
+
221
+ variant = variants.find { |v| v[:name] == name }
222
+ raise VariantItemNotFound, "type: #{variant_type}, name: #{name}" if variant.nil?
223
+
224
+ ScaleRb.encode_uint('u8', variant._get(:index)) + encode_composite(variant, the_value, registry)
225
+ end
226
+
227
+ def _encode_types(ids, values, registry)
228
+ _encode_types_without_merge(ids, values, registry).flatten
229
+ end
230
+
231
+ def _encode_types_with_hashers(values, type_ids, registry, hashers)
232
+ if !hashers.nil? && hashers.length != type_ids.length
233
+ raise ScaleRb::LengthNotEqualErr, "type_ids length: #{type_ids.length}, hashers length: #{hashers.length}"
234
+ end
235
+
236
+ bytes_array = _encode_types_without_merge(type_ids, values, registry)
237
+ bytes_array.each_with_index.reduce([]) do |memo, (bytes, i)|
238
+ memo + Hasher.apply_hasher(hashers[i], bytes)
239
+ end
240
+ end
241
+
242
+ # return: [value1_bytes, value2_bytes, ...]
243
+ def _encode_types_without_merge(ids, values, registry)
244
+ raise ScaleRb::LengthNotEqualErr, "types: #{ids}, values: #{values.inspect}" if ids.length != values.length
245
+
246
+ ids.map.with_index do |type_id, i|
247
+ encode(type_id, values[i], registry)
248
+ end
249
+ end
250
+ end
251
+ end
data/lib/registry.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScaleRb
4
+ # A `registry` is a ruby Hash object, key is the type name, value is the type definition or mapped type name.
5
+ # A `config` contains the complete versioned type definition for a network.
6
+ # https://github.com/polkadot-js/api/blob/master/packages/types-known/src/spec/polkadot.ts
7
+ def self.build_registry_from_config(config, spec_version)
8
+ version = config[:versioned].find do |item|
9
+ item[:minmax].include?(spec_version)
10
+ end
11
+ config[:shared_types].merge(version.nil? ? {} : version[:types])
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module ScaleRb
2
+ VERSION = "0.1.0"
3
+ end
data/lib/scale_rb.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scale_rb/version'
4
+ require 'logger'
5
+
6
+ # scale codec
7
+ require 'monkey_patching'
8
+ require 'codec'
9
+ require 'portable_codec'
10
+
11
+ # metadata types, decoding and helpers
12
+ require 'metadata'
13
+ require 'metadata_v14'
14
+
15
+ require 'hasher'
16
+ require 'storage_helper'
17
+
18
+ # client
19
+ require 'client/rpc'
20
+ require 'client/client'
21
+
22
+ # get registry from config
23
+ require 'registry'
24
+
25
+ module ScaleRb
26
+ class << self
27
+ attr_writer :logger
28
+
29
+ def logger
30
+ @logger ||= Logger.new($stdout)
31
+ @logger.level = Logger::INFO
32
+ @logger
33
+ end
34
+
35
+ def debug(key, value)
36
+ logger.debug "#{key.rjust(15)}: #{value}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StorageHelper
4
+ class << self
5
+ # params:
6
+ # pallet_name: module name
7
+ # method_name: storage name
8
+ # params: {
9
+ # values: values,
10
+ # type_ids: type_ids,
11
+ # hashers: hashers,
12
+ # }
13
+ # registry: portable_types_registry
14
+ def encode_storage_key(pallet_name, method_name, params = nil, registry = nil)
15
+ pallet_method_key = Hasher.twox128(pallet_name) + Hasher.twox128(method_name)
16
+
17
+ if params.nil?
18
+ pallet_method_key
19
+ else
20
+ values = params[:values]
21
+ type_ids = params[:type_ids]
22
+ hashers = params[:hashers]
23
+
24
+ pallet_method_key + PortableCodec._encode_types_with_hashers(values, type_ids, registry, hashers)
25
+ end
26
+ end
27
+
28
+ def build_params(param_values, storage_key_type_id, hashers, registry)
29
+ type_ids = registry._get(storage_key_type_id)._get(:def)._get(:tuple)
30
+ type_ids = [storage_key_type_id] if type_ids.nil?
31
+ {
32
+ values: param_values,
33
+ type_ids: type_ids,
34
+ hashers: hashers
35
+ }
36
+ end
37
+ end
38
+ end
data/scale_rb.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ require_relative 'lib/scale_rb/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'scale_rb'
5
+ spec.version = ScaleRb::VERSION
6
+ spec.authors = ['Aki Wu']
7
+ spec.email = ['wuminzhe@gmail.com']
8
+
9
+ spec.summary = 'New Ruby SCALE Codec Library'
10
+ spec.description = 'Ruby implementation of the parity SCALE data format'
11
+ spec.homepage = 'https://github.com/wuminzhe/scale_rb'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
14
+
15
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ # for hashers
29
+ spec.add_dependency 'blake2b_rs', '~> 0.1.2'
30
+ spec.add_dependency 'xxhash'
31
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scale_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aki Wu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: blake2b_rs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: xxhash
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Ruby implementation of the parity SCALE data format
42
+ email:
43
+ - wuminzhe@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - ".rubocop.yml"
51
+ - ".travis.yml"
52
+ - CODE_OF_CONDUCT.md
53
+ - Gemfile
54
+ - Gemfile.lock
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - bin/console
59
+ - bin/metadata
60
+ - bin/setup
61
+ - lib/client/client.rb
62
+ - lib/client/rpc.rb
63
+ - lib/codec.rb
64
+ - lib/hasher.rb
65
+ - lib/metadata.rb
66
+ - lib/metadata_v14.rb
67
+ - lib/monkey_patching.rb
68
+ - lib/portable_codec.rb
69
+ - lib/registry.rb
70
+ - lib/scale_rb.rb
71
+ - lib/scale_rb/version.rb
72
+ - lib/storage_helper.rb
73
+ - scale_rb.gemspec
74
+ homepage: https://github.com/wuminzhe/scale_rb
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ homepage_uri: https://github.com/wuminzhe/scale_rb
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 2.6.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.1.4
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: New Ruby SCALE Codec Library
99
+ test_files: []