scale_rb 0.4.2 → 0.5.0

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