scale_rb 0.4.2 → 0.5.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +21 -0
  3. data/Dockerfile +16 -0
  4. data/Gemfile +4 -4
  5. data/README.md +19 -7
  6. data/bin/console +0 -0
  7. data/bin/setup +0 -0
  8. data/examples/http_client_2.rb +0 -2
  9. data/exe/metadata +9 -11
  10. data/lib/address.rb +1 -1
  11. data/lib/custom_assign.rb +92 -0
  12. data/lib/scale_rb/call_helper.rb +42 -0
  13. data/lib/{client → scale_rb/client}/client_ext.rb +12 -13
  14. data/lib/{client → scale_rb/client}/http_client.rb +1 -1
  15. data/lib/{client → scale_rb/client}/ws_client.rb +11 -15
  16. data/lib/scale_rb/codec.rb +25 -0
  17. data/lib/scale_rb/codec_utils.rb +128 -0
  18. data/lib/scale_rb/decode.rb +164 -0
  19. data/lib/scale_rb/encode.rb +150 -0
  20. data/lib/{hasher.rb → scale_rb/hasher.rb} +10 -8
  21. data/lib/scale_rb/metadata/metadata.rb +114 -0
  22. data/lib/{metadata → scale_rb/metadata}/metadata_v10.rb +0 -17
  23. data/lib/{metadata → scale_rb/metadata}/metadata_v11.rb +0 -17
  24. data/lib/{metadata → scale_rb/metadata}/metadata_v12.rb +0 -17
  25. data/lib/{metadata → scale_rb/metadata}/metadata_v13.rb +0 -17
  26. data/lib/{metadata → scale_rb/metadata}/metadata_v14.rb +18 -18
  27. data/lib/{metadata → scale_rb/metadata}/metadata_v9.rb +0 -17
  28. data/lib/scale_rb/metadata/registry.rb +263 -0
  29. data/lib/scale_rb/metadata/type_exp.rb +286 -0
  30. data/lib/scale_rb/portable_registry.rb +133 -0
  31. data/lib/{storage_helper.rb → scale_rb/storage_helper.rb} +16 -4
  32. data/lib/scale_rb/types.rb +233 -0
  33. data/lib/scale_rb/utils.rb +125 -0
  34. data/lib/scale_rb/version.rb +1 -1
  35. data/lib/scale_rb.rb +20 -26
  36. data/lib/type_enforcer.rb +170 -0
  37. data/scale_rb.gemspec +3 -0
  38. metadata +57 -19
  39. data/lib/codec.rb +0 -450
  40. data/lib/metadata/metadata.rb +0 -137
  41. data/lib/monkey_patching.rb +0 -115
  42. data/lib/portable_codec.rb +0 -285
  43. data/lib/registry.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51b2e982dba83d910e71a3165d0842a84ff8cf5645c03f4fddfd5fe7c972b369
4
- data.tar.gz: 4a4be7435a1821bf40bcdf917a8d148616dae4857c2cbef3d130d7ec4c64bd85
3
+ metadata.gz: cb19be98106d9f8331a9be9812f6252c23d6913cbd98cedcae34bf1a464e7b90
4
+ data.tar.gz: cff1e034d85f130ed7d149628647f020adb9ffc532d9646bceda054007d6bf14
5
5
  SHA512:
6
- metadata.gz: 7bce634b3f620247fcf6dc9bc7737f2835b1e17a1f36f554bb63c83193ba3350377b309095031154d8025fced6d9badcbbda3afd0071101114360bab38872ae4
7
- data.tar.gz: cd56d6ceff533df2d6b9a0c21347cb903f65a8dedb3f2b1b0f285a27649ba16f13e84eeb06249a93c7fc234f4c717b29443bb636a97b1a19c127fbc3c7e54633
6
+ metadata.gz: de658214901aa13071c6b8d0c1aa94c8db0b2cee3c6c1df3207eb0881399c12215ce6c05a7d89c7da115e279a60fbe4688710e5f3fba827d5bd729afb7275e59
7
+ data.tar.gz: 999f5311c1a66933281798c40bbb3b7956e528b3164c264f2a7694fa48c142683e49ff47a185c1e174501b2e8c3b083d2bd513d1e6c37cffff9002d9186908e4
@@ -0,0 +1,21 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
3
+ {
4
+ "name": "scale_rb",
5
+ "build": {
6
+ // Sets the run context to one level up instead of the .devcontainer folder.
7
+ "context": "..",
8
+ // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
9
+ "dockerfile": "../Dockerfile"
10
+ }
11
+ // Features to add to the dev container. More info: https://containers.dev/features.
12
+ // "features": {},
13
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
14
+ // "forwardPorts": [],
15
+ // Uncomment the next line to run commands after the container is created.
16
+ // "postCreateCommand": "cat /etc/os-release",
17
+ // Configure tool-specific properties.
18
+ // "customizations": {},
19
+ // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
20
+ // "remoteUser": "devcontainer"
21
+ }
data/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ FROM ruby:3.1
2
+
3
+ # Dependencies
4
+ RUN apt-get update -qq && apt-get install -y build-essential
5
+ RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
6
+ ENV PATH="/root/.cargo/bin:${PATH}"
7
+
8
+ # Set the working directory
9
+ WORKDIR /scale_rb
10
+ COPY . /scale_rb
11
+
12
+ # Intall gems
13
+ RUN bundle install
14
+
15
+ # default command is to run a shell
16
+ CMD ["bash"]
data/Gemfile CHANGED
@@ -1,8 +1,8 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in scale_rb.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
7
- gem "rspec", "~> 3.0"
8
- gem 'rubocop', group: 'development', require: false
6
+ gem 'rake', '~> 12.0'
7
+ gem 'rspec', '~> 3.0'
8
+ gem 'rubocop', group: 'development', require: false
data/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # ScaleRb
2
2
 
3
- It is still under heavy development. Use the latest version.
4
-
5
3
  ## Installation
6
4
 
7
5
  Add this line to your application's Gemfile:
8
6
 
9
7
  ```ruby
10
- gem 'scale_rb', '~> 0.4.2'
8
+ gem "scale_rb"
11
9
  ```
12
10
 
13
11
  And then execute:
@@ -25,20 +23,34 @@ git clone https://github.com/wuminzhe/scale_rb.git
25
23
  cd scale_rb
26
24
  bundle install
27
25
  bundle exec ruby examples/http_client_1.rb
28
- # CONSOLE_LEVEL=debug bundle exec ruby examples/http_client_1.rb
29
26
  ```
30
27
 
31
28
  ## Development
32
29
 
33
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+ ### Run devcontainer
31
+
32
+ Open the project in vscode, then in the command palette, type `Reopen in Container` to open the project in a devcontainer.
33
+
34
+ ![image](https://github.com/user-attachments/assets/39af785c-5570-46df-9e6e-bf816e7f7b68)
35
+
34
36
 
35
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+ After the devcontainer is opened, you can run the following commands:
38
+
39
+ 1. Tests:
40
+
41
+ ```bash
42
+ bundle exec rspec
43
+ ```
44
+
45
+ 2. Examples:
46
+ ```bash
47
+ bundle exec ruby examples/http_client_1.rb
48
+ ```
36
49
 
37
50
  ## Contributing
38
51
 
39
52
  Bug reports and pull requests are welcome on GitHub at https://github.com/wuminzhe/scale_rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/wuminzhe/scale_rb/blob/master/CODE_OF_CONDUCT.md).
40
53
 
41
-
42
54
  ## License
43
55
 
44
56
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/console CHANGED
File without changes
data/bin/setup CHANGED
File without changes
@@ -14,7 +14,5 @@ def fetch_some_storages(client, block_number)
14
14
  puts "Time taken: #{end_time - start_time} seconds"
15
15
  end
16
16
 
17
- ScaleRb.logger.level = Logger::DEBUG
18
-
19
17
  client = ScaleRb::HttpClient.new('https://polkadot-rpc.dwellir.com')
20
18
  fetch_some_storages(client, 21711742)
data/exe/metadata CHANGED
@@ -5,13 +5,9 @@ require 'scale_rb'
5
5
  require 'json'
6
6
  require 'optparse'
7
7
 
8
- def print_metadata(url, at = nil)
9
- puts JSON.pretty_generate(ScaleRb::HttpClient.get_metadata(url, at))
10
- end
11
-
12
- # ./bin/metadata http://g2.dev.darwinia.network:2234 -b 0x23ebddd6519aaf1b7fc916c3709af13d7a4010943fb53038406581171000a58e
13
- # ./bin/metadata https://rpc.darwinia.network -b 10511703
14
- # ./bin/metadata https://pangoro-rpc.darwinia.network
8
+ # ./exe/metadata https://polkadot-rpc.dwellir.com -b 0xfc2d86c5a2cc92b7770dcc14e74116912c9f848000b5f4ceca6fe1475588692c
9
+ # ./exe/metadata https://polkadot-rpc.dwellir.com -b 22931689
10
+ # ./exe/metadata https://polkadot-rpc.dwellir.com
15
11
  @options = {}
16
12
  parser = OptionParser.new do |opts|
17
13
  opts.banner = 'Usage: metadata [url] [options]'
@@ -26,16 +22,18 @@ else
26
22
  parser.parse!
27
23
  url = ARGV[0]
28
24
 
25
+ client = ScaleRb::HttpClient.new(url)
26
+
27
+ block_hash = nil
29
28
  if @options[:block]
30
29
  if @options[:block].length == 66
31
30
  block_hash = @options[:block]
32
31
  else
33
32
  block_number = @options[:block].to_i
34
- block_hash = ScaleRb::HttpClient.chain_getBlockHash url, block_number
33
+ block_hash = client.chain_getBlockHash(block_number)
35
34
  end
36
- else
37
- block_hash = ScaleRb::HttpClient.chain_getBlockHash url
38
35
  end
39
36
 
40
- print_metadata url, block_hash
37
+ metadata = client.get_metadata(block_hash)
38
+ puts JSON.pretty_generate(metadata)
41
39
  end
data/lib/address.rb CHANGED
@@ -79,7 +79,7 @@ module ScaleRb
79
79
  end
80
80
 
81
81
  input_bytes = ss58_format_bytes.bytes + pubkey_bytes
82
- checksum = Blake2b.hex(SS58_PREFIX.bytes + input_bytes, 64)._to_bytes
82
+ checksum = Utils.hex_to_u8a(Blake2b.hex(SS58_PREFIX.bytes + input_bytes, 64))
83
83
 
84
84
  Base58.binary_to_base58((input_bytes + checksum[0...checksum_length]).pack('C*'), :bitcoin)
85
85
  end
@@ -0,0 +1,92 @@
1
+ def custom_assign(positional_params, keyword_params, args, kwargs = {}, defaults = {})
2
+ assigned = {}
3
+
4
+ # Handle positional arguments (with optional defaults)
5
+ raise ArgumentError, 'Too many positional arguments' if args.length > positional_params.length
6
+
7
+ positional_params.each_with_index do |param, index|
8
+ if args[index]
9
+ assigned[param] = args[index] # Assign from args if available
10
+ elsif args.length == positional_params.length
11
+ assigned[param] = nil
12
+ elsif defaults.key?(param)
13
+ assigned[param] = defaults[param] # Assign default if no argument is provided
14
+ else
15
+ raise ArgumentError, "Missing required positional argument: #{param}"
16
+ end
17
+ end
18
+
19
+ # Handle keyword arguments (with optional defaults)
20
+ kwargs.each_key do |key|
21
+ raise ArgumentError, "Unknown keyword argument: #{key}" unless keyword_params.include?(key)
22
+ end
23
+
24
+ keyword_params.each do |key|
25
+ if kwargs.key?(key)
26
+ assigned[key] = kwargs[key] # Assign from kwargs if available
27
+ elsif defaults.key?(key)
28
+ assigned[key] = defaults[key] # Assign default if not provided
29
+ else
30
+ raise ArgumentError, "Missing required keyword argument: #{key}"
31
+ end
32
+ end
33
+
34
+ # assert_equal(positional_params.length + keyword_params.length, assigned.length)
35
+ assigned
36
+ end
37
+
38
+ def get_method_params(method)
39
+ params = method.parameters
40
+
41
+ positional_params = []
42
+ keyword_params = []
43
+
44
+ params.each do |type, name|
45
+ case type
46
+ when :req, :opt # Required, optional positional arguments
47
+ positional_params << name
48
+ when :key, :keyreq # Keyword arguments
49
+ keyword_params << name
50
+ end
51
+ end
52
+
53
+ [positional_params, keyword_params]
54
+ end
55
+
56
+ def assert_equal(expected, actual)
57
+ raise "Expected #{expected}, but got #{actual}" unless expected === actual
58
+ end
59
+
60
+ def build_assigned_params(method, defaults, args, kwargs)
61
+ positional_params, keyword_params = get_method_params(method)
62
+ custom_assign(positional_params, keyword_params, args, kwargs, defaults)
63
+ end
64
+
65
+ # def my_method(a, b = 2, c:, d: 4); end
66
+
67
+ # defaults = { b: 2, d: 4 }
68
+
69
+ # # my_method(5, c: 3)
70
+ # p build_assigned_params(:my_method, defaults, 5, c: 3)
71
+ # # => {:a=>5, :b=>2, :c=>3, :d=>4}
72
+
73
+ # # my_method(5, 6, c: 3)
74
+ # p build_assigned_params(:my_method, defaults, 5, 6, c: 3)
75
+ # # => {:a=>5, :b=>6, :c=>3, :d=>4}
76
+
77
+ # # my_method(5, 6, c: 3, d: 10)
78
+ # p build_assigned_params(:my_method, defaults, 5, 6, c: 3, d: 10)
79
+ # # => {:a=>5, :b=>6, :c=>3, :d=>10}
80
+
81
+ # # my_method(5, 6, c: 3, d: 10, e: 11)
82
+ # begin
83
+ # build_assigned_params(:my_method, defaults, 5, 6, c: 3, d: 10, e: 11)
84
+ # rescue ArgumentError => e
85
+ # puts e.message # => "Unknown keyword argument: e"
86
+ # end
87
+
88
+ # begin
89
+ # build_assigned_params(:my_method, defaults, 5, 6, 7, c: 3, d: 10)
90
+ # rescue ArgumentError => e
91
+ # puts e.message # => "Too many positional arguments"
92
+ # end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScaleRb
4
+ module CallHelper
5
+ # callbytes's structure is: pallet_index + call_index + argsbytes
6
+ #
7
+ # callbytes examples:
8
+ # "0x0901"._to_bytes
9
+ # "0x05000a1287977578f888bdc1c7627781af1cc000e6ab1300004c31b8d9a798"._to_bytes
10
+ def self.decode_call(callbytes, metadata)
11
+ pallet_index = callbytes[0]
12
+ pallet = Metadata.get_module_by_index(pallet_index, metadata)
13
+
14
+ # Remove the pallet_index
15
+ # The callbytes we used below should not contain the pallet index.
16
+ # This is because the pallet index is not part of the call type.
17
+ # Its structure is: call_index + call_args
18
+ callbytes_without_pallet_index = callbytes[1..]
19
+ calls_type_id = pallet._get(:calls, :type)
20
+ decoded = Codec.decode(
21
+ calls_type_id,
22
+ callbytes_without_pallet_index,
23
+ Metadata.build_registry(metadata)
24
+ )&.first
25
+
26
+ {
27
+ pallet_name: pallet._get(:name),
28
+ call_name: decoded.is_a?(::Hash) ? decoded.keys.first.to_s : decoded.to_s,
29
+ call: decoded
30
+ }
31
+ end
32
+
33
+ # call examples:
34
+ # {:pallet_name=>"Deposit", :call_name=>"claim", :call=>:claim]}
35
+ # {:pallet_name=>"Balances", :call_name=>"transfer", :call=>{:transfer=>{:dest=>[10, 18, 135, 151, 117, 120, 248, 136, 189, 193, 199, 98, 119, 129, 175, 28, 192, 0, 230, 171], :value=>11000000000000000000}}]}
36
+ def self.encode_call(call, metadata)
37
+ calls_type_id = Metadata.get_calls_type_id(call[:pallet_name], metadata)
38
+ pallet_index = Metadata.get_module(call[:pallet_name], metadata)._get(:index)
39
+ [pallet_index] + Codec.encode(calls_type_id, call[:call], Metadata.build_registry(metadata))
40
+ end
41
+ end
42
+ end
@@ -1,12 +1,11 @@
1
1
  module ScaleRb
2
-
3
2
  # This module is used to add extra methods to both the ScaleRb::WsClient ScaleRb::HttpClient
4
3
  module ClientExt
5
4
  # get decoded metadata at block_hash
6
5
  def get_metadata(block_hash = nil)
7
6
  block_hash ||= chain_getHead
8
7
  metadata_hex = state_getMetadata(block_hash)
9
- ScaleRb::Metadata.decode_metadata(metadata_hex.strip._to_bytes)
8
+ Metadata.decode_metadata(metadata_hex)
10
9
  end
11
10
 
12
11
  # Get decoded storage at block_hash
@@ -39,8 +38,8 @@ module ScaleRb
39
38
  item[:changes].map do |change|
40
39
  storage_key = change[0]
41
40
  data = change[1] || default
42
- storage = data.nil? ? nil : PortableCodec.decode(type_id, data._to_bytes, registry)[0]
43
- { storage_key: storage_key, storage: storage }
41
+ storage = data.nil? ? nil : Codec.decode(type_id, Utils.hex_to_u8a(data), registry)[0]
42
+ { storage_key:, storage: }
44
43
  end
45
44
  end.flatten
46
45
  end
@@ -83,7 +82,7 @@ module ScaleRb
83
82
  # 'System',
84
83
  # 'Account',
85
84
  # key = {
86
- # value: [['0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641'._to_bytes]], # [AccountId]
85
+ # value: [[Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')]], # [AccountId]
87
86
  # type: 0,
88
87
  # hashers: ['Blake2128Concat']
89
88
  # },
@@ -101,7 +100,7 @@ module ScaleRb
101
100
  if key
102
101
  if key[:value].nil? || key[:value].empty?
103
102
  # map, but no key's value provided. get all storages under the partial storage key
104
- partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name)._to_hex
103
+ partial_storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name))
105
104
  get_storages_by_partial_key(
106
105
  block_hash,
107
106
  partial_storage_key,
@@ -111,7 +110,8 @@ module ScaleRb
111
110
  )
112
111
  elsif key[:value].length != key[:hashers].length
113
112
  # map with multi parts, but not have all values
114
- partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key, registry)._to_hex
113
+ partial_storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name, key,
114
+ registry))
115
115
  get_storages_by_partial_key(
116
116
  block_hash,
117
117
  partial_storage_key,
@@ -120,12 +120,12 @@ module ScaleRb
120
120
  registry
121
121
  )
122
122
  else
123
- storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key, registry)._to_hex
123
+ storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name, key, registry))
124
124
  data = state_getStorage(storage_key, block_hash)
125
125
  StorageHelper.decode_storage(data, value[:type], value[:modifier] == 'Optional', value[:fallback], registry)
126
126
  end
127
127
  else
128
- storage_key = StorageHelper.encode_storage_key(pallet_name, item_name)._to_hex
128
+ storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name))
129
129
  data = state_getStorage(storage_key, block_hash)
130
130
  StorageHelper.decode_storage(data, value[:type], value[:modifier] == 'Optional', value[:fallback], registry)
131
131
  end
@@ -152,12 +152,12 @@ module ScaleRb
152
152
  if plain
153
153
  [
154
154
  nil,
155
- { type: plain, modifier: modifier, fallback: fallback }
155
+ { type: plain, modifier:, fallback: }
156
156
  ]
157
157
  elsif map
158
158
  [
159
159
  { value: params, type: map._get(:key), hashers: map._get(:hashers) },
160
- { type: map._get(:value), modifier: modifier, fallback: fallback }
160
+ { type: map._get(:value), modifier:, fallback: }
161
161
  ]
162
162
  else
163
163
  raise 'NoSuchStorageType'
@@ -175,11 +175,10 @@ module ScaleRb
175
175
  if key.is_a?(Integer)
176
176
  key.to_i
177
177
  elsif key.is_a?(String) && key.start_with?('0x')
178
- key._to_bytes
178
+ Utils.hex_to_u8a(key)
179
179
  else
180
180
  key
181
181
  end
182
182
  end
183
-
184
183
  end
185
184
  end
@@ -30,7 +30,7 @@ module ScaleRb
30
30
  http.use_ssl = @uri.scheme == 'https'
31
31
 
32
32
  request = Net::HTTP::Post.new(@uri, 'Content-Type' => 'application/json')
33
- request.body = { jsonrpc: '2.0', method: method, params: params, id: Time.now.to_i }.to_json
33
+ request.body = { jsonrpc: '2.0', method:, params:, id: Time.now.to_i }.to_json
34
34
  ScaleRb.logger.debug "—→ #{request.body}"
35
35
 
36
36
  # https://docs.ruby-lang.org/en/master/Net/HTTPResponse.html
@@ -6,7 +6,6 @@ require_relative 'client_ext'
6
6
 
7
7
  module ScaleRb
8
8
  class WsClient
9
-
10
9
  class << self
11
10
  # @param [string] url
12
11
  def start(url)
@@ -29,7 +28,7 @@ module ScaleRb
29
28
  end
30
29
  end
31
30
 
32
- client.supported_methods = client.rpc_methods()[:methods]
31
+ client.supported_methods = client.rpc_methods[:methods]
33
32
  yield client
34
33
 
35
34
  recv_task.wait
@@ -37,7 +36,6 @@ module ScaleRb
37
36
  recv_task&.stop
38
37
  end
39
38
  end
40
-
41
39
  end
42
40
 
43
41
  private
@@ -45,11 +43,10 @@ module ScaleRb
45
43
  def parse_message(message)
46
44
  message.parse
47
45
  rescue StandardError => e
48
- Console::Event::Failure.for(e).emit(self, "Parse message failed!")
46
+ Console::Event::Failure.for(e).emit(self, 'Parse message failed!')
49
47
  nil
50
48
  end
51
49
  end
52
-
53
50
  end
54
51
  end
55
52
 
@@ -102,9 +99,9 @@ module ScaleRb
102
99
  def unsubscribe(method, subscription_id)
103
100
  return unless method.include?('unsubscribe')
104
101
 
105
- if @subscription_handler.unsubscribe(subscription_id)
106
- request(method, [subscription_id])
107
- end
102
+ return unless @subscription_handler.unsubscribe(subscription_id)
103
+
104
+ request(method, [subscription_id])
108
105
  end
109
106
 
110
107
  def handle_response(response)
@@ -116,14 +113,14 @@ module ScaleRb
116
113
  ScaleRb.logger.info "Received an unknown response: #{response}"
117
114
  end
118
115
  rescue StandardError => e
119
- Console::Event::Failure.for(e).emit(self, "Handle response failed!")
116
+ Console::Event::Failure.for(e).emit(self, 'Handle response failed!')
120
117
  end
121
118
 
122
119
  def read_message
123
120
  loop do
124
121
  return @connection.read
125
122
  rescue StandardError => e
126
- Console::Event::Failure.for(e).emit(self, "Read message from connection failed!")
123
+ Console::Event::Failure.for(e).emit(self, 'Read message from connection failed!')
127
124
  sleep 1
128
125
  retry
129
126
  end
@@ -138,7 +135,7 @@ module ScaleRb
138
135
  response_future.resolve(response[:result])
139
136
  })
140
137
 
141
- request = { jsonrpc: '2.0', id: @request_id, method: method, params: params }
138
+ request = { jsonrpc: '2.0', id: @request_id, method:, params: }
142
139
  ScaleRb.logger.debug "—→ #{request}"
143
140
  @connection.write(request.to_json)
144
141
 
@@ -186,10 +183,9 @@ module ScaleRb
186
183
  subscription_id = notification.dig(:params, :subscription)
187
184
  return if subscription_id.nil?
188
185
 
189
- if @callbacks.key?(subscription_id)
190
- @callbacks[subscription_id].call(notification)
191
- end
186
+ return unless @callbacks.key?(subscription_id)
187
+
188
+ @callbacks[subscription_id].call(notification)
192
189
  end
193
190
  end
194
-
195
191
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'decode'
4
+ require_relative 'encode'
5
+
6
+ module ScaleRb
7
+ module Codec
8
+ class Error < StandardError; end
9
+ class TypeNotFound < Error; end
10
+ class TypeNotImplemented < Error; end
11
+ class CompositeInvalidValue < Error; end
12
+ class ArrayLengthNotEqual < Error; end
13
+ class VariantItemNotFound < Error; end
14
+ class VariantIndexOutOfRange < Error; end
15
+ class VariantInvalidValue < Error; end
16
+ class VariantFieldsLengthNotMatch < Error; end
17
+ class LengthNotEqualErr < Error; end
18
+ class NotEnoughBytesError < Error; end
19
+ class Unreachable < Error; end
20
+ class InvalidBytesError < Error; end
21
+
22
+ extend Decode
23
+ extend Encode
24
+ end
25
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScaleRb
4
+ module CodecUtils
5
+ module InternalDecodeUtils
6
+ extend TypeEnforcer
7
+ include Types
8
+
9
+ __ :decode_uint,
10
+ { type: String.constrained(format: /\A[Uu]\d+\z/), bytes: U8Array },
11
+ DecodeResult[UnsignedInteger]
12
+ def decode_uint(type, bytes)
13
+ bit_length = type[1..].to_i
14
+ byte_length = bit_length / 8
15
+ raise Codec::NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
16
+
17
+ value = Utils.u8a_to_uint(bytes[0...byte_length].reverse)
18
+ # debug 'value', value
19
+ [
20
+ value,
21
+ bytes[byte_length..]
22
+ ]
23
+ end
24
+
25
+ __ :decode_int, { type: String.constrained(format: /\A[Ii]\d+\z/), bytes: U8Array }, DecodeResult[Integer]
26
+ def decode_int(type, bytes)
27
+ bit_length = type[1..].to_i
28
+ byte_length = bit_length / 8
29
+ raise Codec::NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
30
+
31
+ value = Utils.u8a_to_int(bytes[0...byte_length].reverse, bit_length)
32
+ # debug 'value', value
33
+ [
34
+ value,
35
+ bytes[byte_length..]
36
+ ]
37
+ end
38
+
39
+ __ :decode_str, { bytes: U8Array }, DecodeResult[String]
40
+ def decode_str(bytes)
41
+ length, remaining_bytes = _do_decode_compact(bytes)
42
+ raise Codec::NotEnoughBytesError, 'type: String' if remaining_bytes.length < length
43
+
44
+ [Utils.u8a_to_utf8(remaining_bytes[0...length]), remaining_bytes[length..]]
45
+ end
46
+
47
+ __ :decode_boolean, { bytes: U8Array }, DecodeResult[Bool]
48
+ def decode_boolean(bytes)
49
+ value = case bytes[0]
50
+ when 0x00 then false
51
+ when 0x01 then true
52
+ else raise Codec::InvalidBytesError, 'type: Boolean'
53
+ end
54
+ [value, bytes[1..]]
55
+ end
56
+
57
+ # TODO: inner type decoding
58
+ __ :decode_compact, { bytes: U8Array }, DecodeResult[UnsignedInteger]
59
+ def decode_compact(bytes)
60
+ _do_decode_compact(bytes)
61
+ end
62
+
63
+ private
64
+
65
+ def _do_decode_compact(bytes)
66
+ case bytes[0] & 3
67
+ when 0 then [bytes[0] >> 2, bytes[1..]]
68
+ when 1 then [Utils.u8a_to_uint(bytes[0..1].reverse) >> 2, bytes[2..]]
69
+ when 2 then [Utils.u8a_to_uint(bytes[0..3].reverse) >> 2, bytes[4..]]
70
+ when 3
71
+ length = 4 + (bytes[0] >> 2)
72
+ [Utils.u8a_to_uint(bytes[1..length].reverse), bytes[length + 1..]]
73
+ else
74
+ raise Codec::Unreachable, 'type: Compact'
75
+ end
76
+ end
77
+ end
78
+
79
+ module InternalEncodeUtils
80
+ extend TypeEnforcer
81
+ include Types
82
+
83
+ __ :encode_uint, { type: String.constrained(format: /\A[Uu]\d+\z/), value: Integer }, U8Array
84
+ def encode_uint(type, value)
85
+ raise InvalidValueError, "type: #{type}, value: #{value.inspect}" unless value.is_a?(::Integer)
86
+
87
+ bit_length = type[1..].to_i
88
+ Utils.int_to_u8a(value, bit_length).reverse
89
+ end
90
+
91
+ __ :encode_int, { type: String.constrained(format: /\A[Ii]\d+\z/), value: Integer }, U8Array
92
+ def encode_int(_type, _value)
93
+ raise NotImplemented, 'encode_int'
94
+ # raise InvalidValueError, "type: #{type}, value: #{value.inspect}" unless value.is_a?(Integer)
95
+ #
96
+ # bit_length = type[1..].to_i
97
+ # Utils.int_to_u8a(value, bit_length).reverse
98
+ end
99
+
100
+ __ :encode_str, { string: String }, U8Array
101
+ def encode_str(string)
102
+ body = string.unpack('C*')
103
+ encode_compact(body.length) + body
104
+ end
105
+
106
+ __ :encode_boolean, { value: Bool }, U8Array
107
+ def encode_boolean(value)
108
+ return [0x00] if value == false
109
+ return [0x01] if value == true
110
+
111
+ raise InvalidValueError, "type: Bool, value: #{value.inspect}"
112
+ end
113
+
114
+ __ :encode_compact, { value: Integer }, U8Array
115
+ def encode_compact(value)
116
+ return [value << 2] if value.between?(0, 63)
117
+ return Utils.int_to_u8a(((value << 2) + 1)).reverse if value < 2**14
118
+ return Utils.int_to_u8a(((value << 2) + 2)).reverse if value < 2**30
119
+
120
+ bytes = Utils.int_to_u8a(value).reverse
121
+ [(((bytes.length - 4) << 2) + 3)] + bytes
122
+ end
123
+ end
124
+
125
+ extend InternalDecodeUtils
126
+ extend InternalEncodeUtils
127
+ end
128
+ end