scale_rb 0.1.0

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