From 3f66b0b56f9d4281a0c9a1b48898aff869dd0f7d Mon Sep 17 00:00:00 2001 From: Aki Wu Date: Tue, 15 Oct 2024 19:57:50 +0800 Subject: [PATCH 1/4] update --- lib/scale_rb.rb | 1 + lib/scale_rb/extrinsic_helper.rb | 34 ++ lib/scale_rb/metadata/metadata.rb | 15 +- lib/scale_rb/metadata/metadata_v14.rb | 12 +- lib/scale_rb/metadata/registry.rb | 269 ------------- lib/scale_rb/metadata/type_exp.rb | 286 ------------- lib/scale_rb/old_registry.rb | 551 ++++++++++++++++++++++++++ lib/scale_rb/portable_registry.rb | 4 + spec/extrinsic_helper_spec.rb | 155 ++++++++ spec/metadata_spec.rb | 14 + spec/tokenizer_spec.rb | 2 +- spec/type_exp_spec.rb | 25 +- 12 files changed, 792 insertions(+), 576 deletions(-) create mode 100644 lib/scale_rb/extrinsic_helper.rb delete mode 100644 lib/scale_rb/metadata/registry.rb delete mode 100644 lib/scale_rb/metadata/type_exp.rb create mode 100644 lib/scale_rb/old_registry.rb create mode 100644 spec/extrinsic_helper_spec.rb diff --git a/lib/scale_rb.rb b/lib/scale_rb.rb index 0276459..523bf70 100644 --- a/lib/scale_rb.rb +++ b/lib/scale_rb.rb @@ -21,6 +21,7 @@ def debug(key, value) require 'scale_rb/types' require 'scale_rb/portable_registry' +require 'scale_rb/old_registry' require 'scale_rb/codec' require 'scale_rb/metadata/metadata' diff --git a/lib/scale_rb/extrinsic_helper.rb b/lib/scale_rb/extrinsic_helper.rb new file mode 100644 index 0000000..0319ac7 --- /dev/null +++ b/lib/scale_rb/extrinsic_helper.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ScaleRb + module ExtrinsicHelper + def decode_extrinsic(bytes, metadata_prefixed) + meta = bytes[0] + signed = (meta & 0x80) == 0x80 + version = (meta & 0x7f) + + raise "Unsupported version: #{version}" unless version == 4 + + nil unless signed + end + + def patch_types(registry, metadata_prefixed) + add_signed_extensions_type(metadata_prefixed.signed_extensions, registry) + end + + private + + def add_signed_extensions_type(signed_extensions, registry) + type = Types::StructType.new( + fields: signed_extensions.map do |signed_extension| + Types::Field.new( + name: Utils.camelize(signed_extension._get(:identifier)), + type: signed_extension._get(:type) + ) + end, + registry: + ) + registry.add_type(type) + end + end +end diff --git a/lib/scale_rb/metadata/metadata.rb b/lib/scale_rb/metadata/metadata.rb index 016ebc6..cd4f9cb 100644 --- a/lib/scale_rb/metadata/metadata.rb +++ b/lib/scale_rb/metadata/metadata.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative './registry' - require_relative './metadata_v9' require_relative './metadata_v10' require_relative './metadata_v11' @@ -17,14 +15,13 @@ class << self def decode_metadata(hex) bytes = ScaleRb::Utils.hex_to_u8a(hex) - registry = ScaleRb::Metadata::Registry.new TYPES + registry = ScaleRb::OldRegistry.new TYPES metadata, = ScaleRb::Codec.decode('MetadataPrefixed', bytes, registry) metadata end def build_registry(metadata_prefixed) - types = ScaleRb::Utils.get(metadata_prefixed, :metadata, :V14, :lookup, :types) - ScaleRb::PortableRegistry.new(types) + MetadataV14.build_registry(metadata_prefixed) end def get_module(pallet_name, metadata_prefixed) @@ -74,6 +71,14 @@ def get_call_type(pallet_name, call_name, metadata_prefixed) Metadata.const_get("Metadata#{version.upcase}").get_call_type(pallet_name, call_name, metadata_prefixed) end + + def signature_type(metadata_prefixed) + MetadataV14.signature_type(metadata_prefixed) + end + + def signed_extensions(metadata_prefixed) + MetadataV14.signed_extensions(metadata_prefixed) + end end TYPES = { diff --git a/lib/scale_rb/metadata/metadata_v14.rb b/lib/scale_rb/metadata/metadata_v14.rb index 0bca39f..790cfcd 100644 --- a/lib/scale_rb/metadata/metadata_v14.rb +++ b/lib/scale_rb/metadata/metadata_v14.rb @@ -4,9 +4,9 @@ module ScaleRb module Metadata module MetadataV14 class << self - def build_registry(metadata) - types = metadata._get(:lookup, :types) - ScaleRb.build_types(types) + def build_registry(metadata_prefixed) + types = ScaleRb::Utils.get(metadata_prefixed, :metadata, :V14, :lookup, :types) + ScaleRb::PortableRegistry.new(types) end def get_module(pallet_name, metadata_prefixed) @@ -50,6 +50,12 @@ def get_call_type(pallet_name, call_name, metadata_prefixed) variant._get(:name).downcase == call_name.downcase end end + + def signature_type(metadata_prefixed); end + + def signed_extensions(metadata_prefixed) + ScaleRb::Utils.get(metadata_prefixed, :metadata, :V14, :extrinsic, :signedExtensions) + end end TYPES = { diff --git a/lib/scale_rb/metadata/registry.rb b/lib/scale_rb/metadata/registry.rb deleted file mode 100644 index b8c6c59..0000000 --- a/lib/scale_rb/metadata/registry.rb +++ /dev/null @@ -1,269 +0,0 @@ -# frozen_string_literal: true - -require_relative './type_exp' -require_relative '../types' - -# rubocop:disable all -module ScaleRb - module Metadata - class Registry - include Types - - # Map name to index of type in `types` array - # % lookup :: String -> Integer - attr_reader :lookup - - # % keys :: Integer -> String - attr_reader :keys - - # % types :: Array - attr_reader :types - - attr_reader :old_types - - # % initialize :: Hash -> void - def initialize(old_types) - @old_types = old_types - @lookup = {} - @keys = {} - @types = [] - - build() - end - - def build() - @old_types.keys.each do |name| - use(name.to_s) - end - end - - def [](identifier) - if identifier.is_a?(::Integer) - @types[identifier] - elsif identifier.is_a?(::String) - @types[use(identifier)] - else - raise "Unknown identifier type: #{identifier.class}" - end - end - - def inspect - "registry(#{@types.length} types)" - end - - def to_s - @types.map.with_index do |type, index| - "#{@keys[index]} => #{type.to_s}" - end.join("\n") - end - - # % use :: String -> Integer - def use(old_type_exp) - raise "Empty old_type_exp: #{old_type_exp}" if old_type_exp.nil? || old_type_exp.strip == '' - - ast_type = TypeExp.parse(old_type_exp) - raise "No AST type for #{old_type_exp}" if ast_type.nil? - - key = ast_type.to_s - ti = lookup[key] - return ti if ti - - ti = @types.length - @types[ti] = "Placeholder" - @lookup[key] = ti - @keys[ti] = key - @types[ti] = build_portable_type(ast_type) - ti - end - - # % build_portable_type :: NamedType | ArrayType | TupleType -> PortableType - # __ :build_portable_type, { ast_type: TypedArray[TypeExp::ArrayType | TypeExp::TupleType | TypeExp::NamedType] } => PortableType - def build_portable_type(ast_type) - case ast_type - when TypeExp::ArrayType - ArrayType.new(use(ast_type.item), ast_type.len, registry: self) - when TypeExp::TupleType - TupleType.new(ast_type.params.map { |param| use(param) }) - when TypeExp::NamedType - build_portable_type_from_named_type(ast_type) - else - raise "Unknown type: #{ast_type.class}" - end - end - - # % build_portable_type_from_named_type :: NamedType -> PortableType - def build_portable_type_from_named_type(named_type) - name = named_type.name - params = named_type.params - - definition = @old_types[name.to_sym] - return build_from_definition(name, definition) if definition - - primitive = as_primitive(name) - return primitive if primitive - - case name - when 'Vec' - item_index = use(params[0].to_s) - SequenceType.new(type: item_index, registry: self) - when 'Option' - item_index = use(params[0].to_s) - VariantType.option(item_index, self) - when 'Result' - ok_index = use(params[0].to_s) - err_index = use(params[1].to_s) - VariantType.result(ok_index, err_index, self) - when 'Compact' - # item_index = use(params[0].to_s) - # CompactType.new(type: item_index, registry: self) - CompactType.new - when 'Null' - UnitType.new - else - raise "Unknown type: #{name}" - end - end - - # % as_primitive :: String -> PrimitiveType | nil - def as_primitive(name) - case name.downcase - when /^i\d+$/ - PrimitiveType.new(primitive: "I#{name[1..]}".to_sym) - when /^u\d+$/ - PrimitiveType.new(primitive: "U#{name[1..]}".to_sym) - when /^bool$/ - PrimitiveType.new(primitive: :Bool) - when /^str$/, /^text$/ - PrimitiveType.new(primitive: :Str) - else - nil - end - end - - # % build_from_definition :: String -> OldTypeDefinition -> PortableType | TypeAlias - # - # type OldTypeDefinition = String | OldEnumDefinition | OldStructDefinition - # type OldEnumDefinition = { - # _enum: String[] | Hash, - # } - # type OldStructDefinition = { - # _struct: Hash - # } - def build_from_definition(name, definition) # rubocop:disable Metrics/MethodLength - case definition - when String - # TypeAlias.new(name, use(definition)) - alias_type_id = use(definition) - # p "alias_type_id: #{alias_type_id}" - types[alias_type_id] - when Hash - if definition[:_enum] - _build_portable_type_from_enum_definition(definition) - elsif definition[:_set] - raise 'Sets are not supported' - else - _build_portable_type_from_struct_definition(definition) - end - end - end - - private - - def _indexed_enum?(definition) - definition[:_enum].is_a?(::Hash) && definition[:_enum].values.all? { |value| value.is_a?(::Integer) } - end - - # % _build_portable_type_from_enum_definition :: Hash -> VariantType - def _build_portable_type_from_enum_definition(definition) - variants = - if definition[:_enum].is_a?(::Array) - # Simple array enum: - # { - # _enum: ['A', 'B', 'C'] - # } - definition[:_enum].map.with_index do |variant_name, index| - SimpleVariant.new(name: variant_name.to_sym, index:) - end - elsif definition[:_enum].is_a?(::Hash) - if _indexed_enum?(definition) - # Indexed enum: - # { - # _enum: { - # Variant1: 0, - # Variant2: 1, - # Variant3: 2 - # } - # } - definition[:_enum].map do |variant_name, index| - SimpleVariant.new(name: variant_name, index:) - end - else - # Mixed enum: - # { - # _enum: { - # A: 'u32', - # B: {a: 'u32', b: 'u32'}, - # C: null, - # D: ['u32', 'u32'] - # } - # } - definition[:_enum].map.with_index do |(variant_name, variant_def), index| - case variant_def - when ::String - TupleVariant.new( - name: variant_name, - index:, - tuple: TupleType.new( - tuple: [use(variant_def)], - registry: self - ), - ) - when ::Array - TupleVariant.new( - name: variant_name, - index:, - tuple: TupleType.new( - tuple: variant_def.map { |field_type| use(field_type) }, - registry: self - ) - ) - when ::Hash - StructVariant.new( - name: variant_name, - index:, - struct: StructType.new( - fields: variant_def.map do |field_name, field_type| - Field.new(name: field_name.to_s, type: use(field_type)) - end, - registry: self - ) - ) - else - raise "Unknown variant type for #{variant_name}: #{variant_def.class}" - end - end - end - end - VariantType.new(variants:, registry: self) - end - - # % _build_portable_type_from_struct_definition :: Hash -> StructType - def _build_portable_type_from_struct_definition(definition) - fields = definition.map do |field_name, field_type| - Field.new(name: field_name.to_s, type: use(field_type)) - end - StructType.new(fields:, registry: self) - end - end - end -end - -# require_relative '../../metadata/metadata' - -# begin -# registry = ScaleRb::Metadata::Registry.new ScaleRb::Metadata::TYPES -# puts registry -# rescue StandardError => e -# puts e.message -# puts e.backtrace.join("\n") -# end diff --git a/lib/scale_rb/metadata/type_exp.rb b/lib/scale_rb/metadata/type_exp.rb deleted file mode 100644 index 1c8b500..0000000 --- a/lib/scale_rb/metadata/type_exp.rb +++ /dev/null @@ -1,286 +0,0 @@ -# frozen_string_literal: true - -module ScaleRb - module Metadata - module TypeExp - class Tokenizer - attr_reader :tokens, :index - - # % tokenize :: String -> [String] - def initialize(type_exp) - @tokens = tokenize(type_exp) - @index = 0 - end - - # % next_token :: -> String - def next_token - token = @tokens[@index] - @index += 1 - token - end - - # % peek_token :: -> String - def peek_token - @tokens[@index] - end - - # % eof? :: -> Bool - def eof? - @index >= @tokens.length - end - - private - - def tokenize(type_exp) - tokens = [] - current_token = '' - - type_exp.each_char do |char| - case char - when /[a-zA-Z0-9_]/ - current_token += char - when ':', '<', '>', '(', ')', '[', ']', ',', ';', '&', "'" - tokens << current_token unless current_token.empty? - if char == ':' && tokens.last == ':' - tokens[-1] = '::' - else - tokens << char - end - current_token = '' - when /\s/ - tokens << current_token unless current_token.empty? - current_token = '' - else - raise abort - end - end - - tokens << current_token unless current_token.empty? - tokens - end - end - - class NamedType - attr_reader :name, :params - - def initialize(name, params) - @name = name - @params = params - end - - def to_s - params.empty? ? name : "#{name}<#{params.map(&:to_s).join(', ')}>" - end - end - - class ArrayType - attr_reader :item, :len - - def initialize(item, len) - @item = item - @len = len - end - - def to_s - "[#{item}; #{len}]" - end - end - - class TupleType - attr_reader :params - - def initialize(params) - @params = params - end - - def to_s - "(#{params.map(&:to_s).join(', ')})" - end - end - - # % print :: NamedType | ArrayType | TupleType -> String - def self.print(type) - type.to_s - end - - # % parse :: String -> NamedType | ArrayType | TupleType - def self.parse(type_exp) - TypeExpParser.new(type_exp).parse - end - - class TypeExpParser - def initialize(type_exp) - @type_exp = type_exp - @tokenizer = Tokenizer.new(type_exp) - @current_token = @tokenizer.next_token - end - - def parse - build_type - end - - private - - # Consume and return the current token, or nil if it doesn't equal the expected token. - def expect(token) - return unless @current_token == token - - current_token = @current_token - @current_token = @tokenizer.next_token - current_token - end - - def expect!(token) - expect(token) || raise("Expected #{token}, got #{@current_token}") - end - - # Consume and return the current token if it matches the expected regex pattern. - def expect_regex(pattern) - return unless pattern.match?(@current_token) - - current_token = @current_token - @current_token = @tokenizer.next_token - current_token - end - - def expect_regex!(pattern) - expect_regex(pattern) || raise("Expected current token matching #{pattern.inspect}, got #{@current_token}") - end - - # Consume and return a natural number (integer) if the current token matches. - def expect_nat - expect_regex(/^\d+$/)&.to_i - end - - def expect_nat! - expect_nat || raise("Expected natural number, got #{@current_token}") - end - - def expect_name - expect_regex(/^[a-zA-Z]\w*$/) - end - - def expect_name! - expect_name || raise("Expected name, got #{@current_token}") - end - - def list(sep, &block) - result = [] - item = block.call - return result if item.nil? - - result << item - while expect(sep) - item = block.call - break if item.nil? # (A, B,) - - result << item - end - result - end - - def build_tuple_type - return nil unless expect('(') - - params = list(',') { build_type } - expect!(')') - - TupleType.new(params) - end - - # [u8; 16; H128] - # [u8; 16] - def build_array_type - return nil unless expect('[') - - item = build_type - raise "Expected array item, got #{@current_token}" if item.nil? - - expect!(';') - len = expect_nat! - - # [u8; 16; H128] - if expect(';') - expect_name! # Just consume the name - end - - expect!(']') - ArrayType.new(item, len) - end - - def build_named_type - name = nil - trait = nil - item = nil - - if expect('<') - # Handle trait syntax: ::Type - # name trait item - # '::Inherent' -> 'InherentOfflineReport' - # '' -> 'Compact' - # '>::Proposal' -> 'Proposal' - name = build_named_type.name - expect!('as') - trait = build_named_type.name - expect!('>') - else - name = expect_name - return if name.nil? - end - - # Consume the :: and get the next name - item = expect_name while expect('::') - - # Handle special cases - # From subsquid's code. But where are these coming from? - if name == 'InherentOfflineReport' && name == trait && item == 'Inherent' - # Do nothing - elsif name == 'exec' && item == 'StorageKey' - name = 'ContractStorageKey' - elsif name == 'Lookup' && item == 'Source' - name = 'LookupSource' - elsif name == 'Lookup' && item == 'Target' - name = 'LookupTarget' - elsif item - # '::Item' will raise error - raise "Expected item, got #{item}" if trait == 'HasCompact' - - name = item - elsif trait == 'HasCompact' # '' - return NamedType.new('Compact', [NamedType.new(name, [])]) - end - - NamedType.new(name, type_parameters) - end - - def type_parameters - if expect('<') - params = list(',') { expect_nat || build_type } - expect!('>') - else - params = [] - end - - params - end - - # &[u8] - # &'static [u8] - def build_pointer_bytes - return nil unless expect('&') # & - - expect("'") && expect!('static') - expect!('[') - expect!('u8') - expect!(']') - NamedType.new('Vec', [NamedType.new('u8', [])]) - end - - # % build_type :: TupleType | ArrayType | NamedType - def build_type - build_tuple_type || build_array_type || build_named_type || build_pointer_bytes - end - end - end - end -end diff --git a/lib/scale_rb/old_registry.rb b/lib/scale_rb/old_registry.rb new file mode 100644 index 0000000..39b3a73 --- /dev/null +++ b/lib/scale_rb/old_registry.rb @@ -0,0 +1,551 @@ +# frozen_string_literal: true + +require_relative './types' + +# rubocop:disable all +module ScaleRb + class OldRegistry + include Types + + # Map name to index of type in `types` array + # % lookup :: String -> Integer + attr_reader :lookup + + # % keys :: Integer -> String + attr_reader :keys + + # % types :: Array + attr_reader :types + + attr_reader :old_types + + # % initialize :: Hash -> void + def initialize(old_types) + @old_types = old_types + @lookup = {} + @keys = {} + @types = [] + + build() + end + + def build() + @old_types.keys.each do |name| + use(name.to_s) + end + end + + def [](identifier) + if identifier.is_a?(::Integer) + @types[identifier] + elsif identifier.is_a?(::String) + @types[use(identifier)] + else + raise "Unknown identifier type: #{identifier.class}" + end + end + + def inspect + "registry(#{@types.length} types)" + end + + def to_s + @types.map.with_index do |type, index| + "#{@keys[index]} => #{type.to_s}" + end.join("\n") + end + + # % use :: String -> Integer + def use(old_type_exp) + raise "Empty old_type_exp: #{old_type_exp}" if old_type_exp.nil? || old_type_exp.strip == '' + + ast_type = TypeExp.parse(old_type_exp) + raise "No AST type for #{old_type_exp}" if ast_type.nil? + + key = ast_type.to_s + ti = lookup[key] + return ti if ti + + ti = @types.length + @types[ti] = "Placeholder" + @lookup[key] = ti + @keys[ti] = key + @types[ti] = build_portable_type(ast_type) + ti + end + + # % build_portable_type :: NamedType | ArrayType | TupleType -> PortableType + # __ :build_portable_type, { ast_type: TypedArray[TypeExp::ArrayType | TypeExp::TupleType | TypeExp::NamedType] } => PortableType + def build_portable_type(ast_type) + case ast_type + when TypeExp::ArrayType + ArrayType.new(use(ast_type.item), ast_type.len, registry: self) + when TypeExp::TupleType + TupleType.new(ast_type.params.map { |param| use(param) }) + when TypeExp::NamedType + build_portable_type_from_named_type(ast_type) + else + raise "Unknown type: #{ast_type.class}" + end + end + + # % build_portable_type_from_named_type :: NamedType -> PortableType + def build_portable_type_from_named_type(named_type) + name = named_type.name + params = named_type.params + + definition = @old_types[name.to_sym] + return build_from_definition(name, definition) if definition + + primitive = as_primitive(name) + return primitive if primitive + + case name + when 'Vec' + item_index = use(params[0].to_s) + SequenceType.new(type: item_index, registry: self) + when 'Option' + item_index = use(params[0].to_s) + VariantType.option(item_index, self) + when 'Result' + ok_index = use(params[0].to_s) + err_index = use(params[1].to_s) + VariantType.result(ok_index, err_index, self) + when 'Compact' + # item_index = use(params[0].to_s) + # CompactType.new(type: item_index, registry: self) + CompactType.new + when 'Null' + UnitType.new + else + raise "Unknown type: #{name}" + end + end + + # % as_primitive :: String -> PrimitiveType | nil + def as_primitive(name) + case name.downcase + when /^i\d+$/ + PrimitiveType.new(primitive: "I#{name[1..]}".to_sym) + when /^u\d+$/ + PrimitiveType.new(primitive: "U#{name[1..]}".to_sym) + when /^bool$/ + PrimitiveType.new(primitive: :Bool) + when /^str$/, /^text$/ + PrimitiveType.new(primitive: :Str) + else + nil + end + end + + # % build_from_definition :: String -> OldTypeDefinition -> PortableType | TypeAlias + # + # type OldTypeDefinition = String | OldEnumDefinition | OldStructDefinition + # type OldEnumDefinition = { + # _enum: String[] | Hash, + # } + # type OldStructDefinition = { + # _struct: Hash + # } + def build_from_definition(name, definition) # rubocop:disable Metrics/MethodLength + case definition + when String + # TypeAlias.new(name, use(definition)) + alias_type_id = use(definition) + # p "alias_type_id: #{alias_type_id}" + types[alias_type_id] + when Hash + if definition[:_enum] + _build_portable_type_from_enum_definition(definition) + elsif definition[:_set] + raise 'Sets are not supported' + else + _build_portable_type_from_struct_definition(definition) + end + end + end + + private + + def _indexed_enum?(definition) + definition[:_enum].is_a?(::Hash) && definition[:_enum].values.all? { |value| value.is_a?(::Integer) } + end + + # % _build_portable_type_from_enum_definition :: Hash -> VariantType + def _build_portable_type_from_enum_definition(definition) + variants = + if definition[:_enum].is_a?(::Array) + # Simple array enum: + # { + # _enum: ['A', 'B', 'C'] + # } + definition[:_enum].map.with_index do |variant_name, index| + SimpleVariant.new(name: variant_name.to_sym, index:) + end + elsif definition[:_enum].is_a?(::Hash) + if _indexed_enum?(definition) + # Indexed enum: + # { + # _enum: { + # Variant1: 0, + # Variant2: 1, + # Variant3: 2 + # } + # } + definition[:_enum].map do |variant_name, index| + SimpleVariant.new(name: variant_name, index:) + end + else + # Mixed enum: + # { + # _enum: { + # A: 'u32', + # B: {a: 'u32', b: 'u32'}, + # C: null, + # D: ['u32', 'u32'] + # } + # } + definition[:_enum].map.with_index do |(variant_name, variant_def), index| + case variant_def + when ::String + TupleVariant.new( + name: variant_name, + index:, + tuple: TupleType.new( + tuple: [use(variant_def)], + registry: self + ), + ) + when ::Array + TupleVariant.new( + name: variant_name, + index:, + tuple: TupleType.new( + tuple: variant_def.map { |field_type| use(field_type) }, + registry: self + ) + ) + when ::Hash + StructVariant.new( + name: variant_name, + index:, + struct: StructType.new( + fields: variant_def.map do |field_name, field_type| + Field.new(name: field_name.to_s, type: use(field_type)) + end, + registry: self + ) + ) + else + raise "Unknown variant type for #{variant_name}: #{variant_def.class}" + end + end + end + end + VariantType.new(variants:, registry: self) + end + + # % _build_portable_type_from_struct_definition :: Hash -> StructType + def _build_portable_type_from_struct_definition(definition) + fields = definition.map do |field_name, field_type| + Field.new(name: field_name.to_s, type: use(field_type)) + end + StructType.new(fields:, registry: self) + end + end +end + +module ScaleRb + class OldRegistry + module TypeExp + class Tokenizer + attr_reader :tokens, :index + + # % tokenize :: String -> [String] + def initialize(type_exp) + @tokens = tokenize(type_exp) + @index = 0 + end + + # % next_token :: -> String + def next_token + token = @tokens[@index] + @index += 1 + token + end + + # % peek_token :: -> String + def peek_token + @tokens[@index] + end + + # % eof? :: -> Bool + def eof? + @index >= @tokens.length + end + + private + + def tokenize(type_exp) + tokens = [] + current_token = '' + + type_exp.each_char do |char| + case char + when /[a-zA-Z0-9_]/ + current_token += char + when ':', '<', '>', '(', ')', '[', ']', ',', ';', '&', "'" + tokens << current_token unless current_token.empty? + if char == ':' && tokens.last == ':' + tokens[-1] = '::' + else + tokens << char + end + current_token = '' + when /\s/ + tokens << current_token unless current_token.empty? + current_token = '' + else + raise abort + end + end + + tokens << current_token unless current_token.empty? + tokens + end + end + + class NamedType + attr_reader :name, :params + + def initialize(name, params) + @name = name + @params = params + end + + def to_s + params.empty? ? name : "#{name}<#{params.map(&:to_s).join(', ')}>" + end + end + + class ArrayType + attr_reader :item, :len + + def initialize(item, len) + @item = item + @len = len + end + + def to_s + "[#{item}; #{len}]" + end + end + + class TupleType + attr_reader :params + + def initialize(params) + @params = params + end + + def to_s + "(#{params.map(&:to_s).join(', ')})" + end + end + + # % print :: NamedType | ArrayType | TupleType -> String + def self.print(type) + type.to_s + end + + # % parse :: String -> NamedType | ArrayType | TupleType + def self.parse(type_exp) + TypeExpParser.new(type_exp).parse + end + + class TypeExpParser + def initialize(type_exp) + @type_exp = type_exp + @tokenizer = Tokenizer.new(type_exp) + @current_token = @tokenizer.next_token + end + + def parse + build_type + end + + private + + # Consume and return the current token, or nil if it doesn't equal the expected token. + def expect(token) + return unless @current_token == token + + current_token = @current_token + @current_token = @tokenizer.next_token + current_token + end + + def expect!(token) + expect(token) || raise("Expected #{token}, got #{@current_token}") + end + + # Consume and return the current token if it matches the expected regex pattern. + def expect_regex(pattern) + return unless pattern.match?(@current_token) + + current_token = @current_token + @current_token = @tokenizer.next_token + current_token + end + + def expect_regex!(pattern) + expect_regex(pattern) || raise("Expected current token matching #{pattern.inspect}, got #{@current_token}") + end + + # Consume and return a natural number (integer) if the current token matches. + def expect_nat + expect_regex(/^\d+$/)&.to_i + end + + def expect_nat! + expect_nat || raise("Expected natural number, got #{@current_token}") + end + + def expect_name + expect_regex(/^[a-zA-Z]\w*$/) + end + + def expect_name! + expect_name || raise("Expected name, got #{@current_token}") + end + + def list(sep, &block) + result = [] + item = block.call + return result if item.nil? + + result << item + while expect(sep) + item = block.call + break if item.nil? # (A, B,) + + result << item + end + result + end + + def build_tuple_type + return nil unless expect('(') + + params = list(',') { build_type } + expect!(')') + + TupleType.new(params) + end + + # [u8; 16; H128] + # [u8; 16] + def build_array_type + return nil unless expect('[') + + item = build_type + raise "Expected array item, got #{@current_token}" if item.nil? + + expect!(';') + len = expect_nat! + + # [u8; 16; H128] + if expect(';') + expect_name! # Just consume the name + end + + expect!(']') + ArrayType.new(item, len) + end + + def build_named_type + name = nil + trait = nil + item = nil + + if expect('<') + # Handle trait syntax: ::Type + # name trait item + # '::Inherent' -> 'InherentOfflineReport' + # '' -> 'Compact' + # '>::Proposal' -> 'Proposal' + name = build_named_type.name + expect!('as') + trait = build_named_type.name + expect!('>') + else + name = expect_name + return if name.nil? + end + + # Consume the :: and get the next name + item = expect_name while expect('::') + + # Handle special cases + # From subsquid's code. But where are these coming from? + if name == 'InherentOfflineReport' && name == trait && item == 'Inherent' + # Do nothing + elsif name == 'exec' && item == 'StorageKey' + name = 'ContractStorageKey' + elsif name == 'Lookup' && item == 'Source' + name = 'LookupSource' + elsif name == 'Lookup' && item == 'Target' + name = 'LookupTarget' + elsif item + # '::Item' will raise error + raise "Expected item, got #{item}" if trait == 'HasCompact' + + name = item + elsif trait == 'HasCompact' # '' + return NamedType.new('Compact', [NamedType.new(name, [])]) + end + + NamedType.new(name, type_parameters) + end + + def type_parameters + if expect('<') + params = list(',') { expect_nat || build_type } + expect!('>') + else + params = [] + end + + params + end + + # &[u8] + # &'static [u8] + def build_pointer_bytes + return nil unless expect('&') # & + + expect("'") && expect!('static') + expect!('[') + expect!('u8') + expect!(']') + NamedType.new('Vec', [NamedType.new('u8', [])]) + end + + # % build_type :: TupleType | ArrayType | NamedType + def build_type + build_tuple_type || build_array_type || build_named_type || build_pointer_bytes + end + end + end + end +end + +# require_relative '../../metadata/metadata' + +# begin +# registry = ScaleRb::Metadata::Registry.new ScaleRb::Metadata::TYPES +# puts registry +# rescue StandardError => e +# puts e.message +# puts e.backtrace.join("\n") +# end diff --git a/lib/scale_rb/portable_registry.rb b/lib/scale_rb/portable_registry.rb index bc5a075..f3e28e4 100644 --- a/lib/scale_rb/portable_registry.rb +++ b/lib/scale_rb/portable_registry.rb @@ -24,6 +24,10 @@ def inspect "a_portable_registry" end + def add_type(type) + @types << type + end + private __ :build_types, {} diff --git a/spec/extrinsic_helper_spec.rb b/spec/extrinsic_helper_spec.rb new file mode 100644 index 0000000..88957ef --- /dev/null +++ b/spec/extrinsic_helper_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'scale_rb' +require 'json' + +module ScaleRb + RSpec.describe StorageHelper do + before(:all) do + data = JSON.parse(File.open(File.join(__dir__, 'assets', 'darwinia-types.json')).read) + @registry = ScaleRb::PortableRegistry.new(data) + end + + it 'can encode storage key without param' do + storage_key = StorageHelper.encode_storage_key('System', 'EventCount') + storage_key = Utils.u8a_to_hex(storage_key) + expect(storage_key).to eql('0x26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850') + end + + it 'can encode storage key with one param' do + # account_id + key = { + value: [Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')], + type: 0, + hashers: ['Blake2_128Concat'] + } + + storage_key = StorageHelper.encode_storage_key( + 'System', + 'Account', + key, + @registry + ) + expect = Utils.hex_to_u8a('0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94bab0fcfc536fa263f3b241cd32f76a8724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641') + expect(storage_key).to eql(expect) + end + + it 'can encode storage key with two param(same hasher)' do + storage_key = StorageHelper.encode_storage_key( + 'ImOnline', + 'AuthoredBlocks', + { + value: [123, Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')], # U32, AccountId + type: 291, # 291 => [4, 0] + hashers: %w[Twox64Concat Twox64Concat] + }, + @registry + ) + expect = Utils.hex_to_u8a('0x2b06af9719ac64d755623cda8ddd9b94b1c371ded9e9c565e89ba783c4d5f5f92a9a1a82315e68fd7b000000a2c377ff1d6261f6724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641') + expect(storage_key).to eql(expect) + end + + it 'can encode storage key with two param(different hasher)' do + storage_key = StorageHelper.encode_storage_key( + 'Multisig', + 'Multisigs', + { + value: [ + Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641'), # AccountId + Utils.hex_to_u8a('0x0101010101010101010101010101010101010101010101010101010101010101') # [U8; 32] array + ], + type: 582, # 582 => [0, 1] + hashers: %w[Twox64Concat Blake2_128Concat] + }, + @registry + ) + expect = Utils.hex_to_u8a('0x7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8da2c377ff1d6261f6724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641c035f853fcd0f0589e30c9e2dc1a0f570101010101010101010101010101010101010101010101010101010101010101') + expect(storage_key).to eql(expect) + end + + it 'can decode system events' do + metadata = JSON.parse(File.open(File.join(__dir__, 'assets', 'darwinia-metadata.1243.json')).read) + data = '0x1800000000000000585f8f09000000000200000001000000040964766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996404b4c00000000000000000000000000000001000000040764766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996623116000000000000000000000000000000010000002f009c266c48f07121181d8424768f0ded0170cc63a6044c6030db06afe5c2251138fd7b0c3aef3876f9f60cecfae80a2e3b9cdd3b6d5d810200000000000000000000000000000000000000000000000000000000004b32200000000000000000000000000000000000000000000000000000000000051210db0ddcce0c5a3514e4396b69edac100b112deb966d7a6ee4ab8423edfc779b58f9ed9f96d0c7ba91d11970ea62f7648a7ba440ebacdcc1023c3ba310280cc7239edd250ff23d036d7d9ffc03377346814463d22f3e50fac3179f49a9c30e642c00000100000030002eabe5c6818731e282b80de1a03f8190426e0dd99c266c48f07121181d8424768f0ded0170cc63a6757a2695ae238d39120f2897d6e555b144c90f62ef457009bd83afc1dafc2e6b0000000001000000000080bf490521000000000000' + storage = StorageHelper.decode_storage3(data, 'System', 'Events', metadata) + expect = + [ + { + phase: { ApplyExtrinsic: 0 }, + event: { + System: { + ExtrinsicSuccess: { + weight: 160_391_000, + class: :Mandatory, + pays_fee: :Yes + } + } + }, + topics: [] + }, + { + phase: { ApplyExtrinsic: 1 }, + event: { + Balances: { + Slashed: { + who: ScaleRb::Utils.hex_to_u8a('0x64766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996'), + amount: 5_000_000 + } + } + }, + topics: [] + }, + { + phase: { ApplyExtrinsic: 1 }, + event: { + Balances: { + Deposit: { + who: ScaleRb::Utils.hex_to_u8a('0x64766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996'), + amount: 1_454_434 + } + } + }, + topics: [] + }, + { + phase: { ApplyExtrinsic: 1 }, + event: { + EVM: { + Log: { + log: { + address: ScaleRb::Utils.hex_to_u8a('0x9c266c48f07121181d8424768f0ded0170cc63a6'), + topics: [ScaleRb::Utils.hex_to_u8a('0x4c6030db06afe5c2251138fd7b0c3aef3876f9f60cecfae80a2e3b9cdd3b6d5d')], + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, + 50, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 18, 16, 219, 13, 220, 206, 12, 90, 53, 20, 228, 57, 107, 105, 237, 172, 16, 11, 17, 45, 235, 150, 109, 122, 110, 228, 171, 132, 35, 237, 252, 119, 155, 88, 249, 237, 159, 150, 208, 199, 186, 145, 209, 25, 112, 234, 98, 247, 100, 138, 123, 164, 64, 235, 172, 220, 193, 2, 60, 59, 163, 16, 40, 12, 199, 35, 158, 221, 37, 15, 242, 61, 3, 109, 125, 159, 252, 3, 55, 115, 70, 129, 68, 99, 210, 47, 62, 80, 250, 195, 23, 159, 73, 169, 195, 14, 100, 44] + } + } + } + }, + topics: [] + }, + { + phase: { ApplyExtrinsic: 1 }, + event: { + Ethereum: { + Executed: { + from: ScaleRb::Utils.hex_to_u8a('0x2eabe5c6818731e282b80de1a03f8190426e0dd9'), + to: ScaleRb::Utils.hex_to_u8a('0x9c266c48f07121181d8424768f0ded0170cc63a6'), + transaction_hash: ScaleRb::Utils.hex_to_u8a('0x757a2695ae238d39120f2897d6e555b144c90f62ef457009bd83afc1dafc2e6b'), + exit_reason: { + Succeed: :Stopped + } + } + } + }, + topics: [] + }, + { + phase: { ApplyExtrinsic: 1 }, + event: { System: { ExtrinsicSuccess: { weight: 141_822_640_000, class: :Normal, pays_fee: :Yes } } }, + topics: [] + } + ] + + expect(storage).to eql(expect) + end + end +end diff --git a/spec/metadata_spec.rb b/spec/metadata_spec.rb index d4fed97..22708ad 100644 --- a/spec/metadata_spec.rb +++ b/spec/metadata_spec.rb @@ -156,5 +156,19 @@ module ScaleRb registry = ScaleRb::Metadata.build_registry(@metadata) expect(registry.types.length).to eq(696) end + + # it 'can get signature type' do + # expect = [ + # { identifier: 'CheckNonZeroSender', type: 686, additionalSigned: 31 }, + # { identifier: 'CheckSpecVersion', type: 687, additionalSigned: 4 }, + # { identifier: 'CheckTxVersion', type: 688, additionalSigned: 4 }, + # { identifier: 'CheckGenesis', type: 689, additionalSigned: 11 }, + # { identifier: 'CheckMortality', type: 690, additionalSigned: 11 }, + # { identifier: 'CheckNonce', type: 692, additionalSigned: 31 }, + # { identifier: 'CheckWeight', type: 693, additionalSigned: 31 }, + # { identifier: 'ChargeAssetTxPayment', type: 694, additionalSigned: 31 } + # ] + # expect(ScaleRb::Metadata.signed_extensions_type(@metadata)).to eq(expect) + # end end end diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 1bfac0f..e948982 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -3,7 +3,7 @@ require 'scale_rb' # rubocop:disable Metrics/BlockLength -RSpec.describe ScaleRb::Metadata::TypeExp::Tokenizer do +RSpec.describe ScaleRb::OldRegistry::TypeExp::Tokenizer do describe '.tokenize' do it 'tokenizes a simple type' do tokenizer = described_class.new('A') diff --git a/spec/type_exp_spec.rb b/spec/type_exp_spec.rb index 35f225c..7c3d047 100644 --- a/spec/type_exp_spec.rb +++ b/spec/type_exp_spec.rb @@ -2,7 +2,7 @@ require 'scale_rb' -RSpec.describe ScaleRb::Metadata::TypeExp do +RSpec.describe ScaleRb::OldRegistry::TypeExp do def self.ast(exp, type) it "AST: #{exp}" do parsed = described_class.parse(exp) @@ -19,33 +19,34 @@ def self.test(exp, result = nil) end describe 'AST parsing' do - ast('A', ScaleRb::Metadata::TypeExp::NamedType.new('A', [])) + ast('A', ScaleRb::OldRegistry::TypeExp::NamedType.new('A', [])) ast('Compact', - ScaleRb::Metadata::TypeExp::NamedType.new('Compact', [])) + ScaleRb::OldRegistry::TypeExp::NamedType.new('Compact', [])) ast('Compact', - ScaleRb::Metadata::TypeExp::NamedType.new('Compact', [ScaleRb::Metadata::TypeExp::NamedType.new('u8', [])])) + ScaleRb::OldRegistry::TypeExp::NamedType.new('Compact', + [ScaleRb::OldRegistry::TypeExp::NamedType.new('u8', [])])) ast('Vec', - ScaleRb::Metadata::TypeExp::NamedType.new('Vec', [ScaleRb::Metadata::TypeExp::NamedType.new('u8', [])])) + ScaleRb::OldRegistry::TypeExp::NamedType.new('Vec', [ScaleRb::OldRegistry::TypeExp::NamedType.new('u8', [])])) ast('[A; 10]', - ScaleRb::Metadata::TypeExp::ArrayType.new(ScaleRb::Metadata::TypeExp::NamedType.new('A', []), 10)) + ScaleRb::OldRegistry::TypeExp::ArrayType.new(ScaleRb::OldRegistry::TypeExp::NamedType.new('A', []), 10)) ast('[u8; 16; H128]', - ScaleRb::Metadata::TypeExp::ArrayType.new(ScaleRb::Metadata::TypeExp::NamedType.new('u8', []), 16)) + ScaleRb::OldRegistry::TypeExp::ArrayType.new(ScaleRb::OldRegistry::TypeExp::NamedType.new('u8', []), 16)) ast( '(A, B, [u8; 5])', - ScaleRb::Metadata::TypeExp::TupleType.new( + ScaleRb::OldRegistry::TypeExp::TupleType.new( [ - ScaleRb::Metadata::TypeExp::NamedType.new('A', []), - ScaleRb::Metadata::TypeExp::NamedType.new( + ScaleRb::OldRegistry::TypeExp::NamedType.new('A', []), + ScaleRb::OldRegistry::TypeExp::NamedType.new( 'B', [] ), - ScaleRb::Metadata::TypeExp::ArrayType.new( - ScaleRb::Metadata::TypeExp::NamedType.new( + ScaleRb::OldRegistry::TypeExp::ArrayType.new( + ScaleRb::OldRegistry::TypeExp::NamedType.new( 'u8', [] ), 5 ) From dc85a2d34199a6b4332e06ca788be57e1ac1da24 Mon Sep 17 00:00:00 2001 From: Aki Wu Date: Sat, 19 Oct 2024 14:52:23 +0800 Subject: [PATCH 2/4] update --- lib/scale_rb.rb | 1 + lib/scale_rb/portable_registry.rb | 4 ++++ lib/scale_rb/runtime_spec.rb | 36 +++++++++++++++++++++++++++++++ spec/runtime_spec_spec.rb | 16 ++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 lib/scale_rb/runtime_spec.rb create mode 100644 spec/runtime_spec_spec.rb diff --git a/lib/scale_rb.rb b/lib/scale_rb.rb index 523bf70..0180f9e 100644 --- a/lib/scale_rb.rb +++ b/lib/scale_rb.rb @@ -25,6 +25,7 @@ def debug(key, value) require 'scale_rb/codec' require 'scale_rb/metadata/metadata' +require 'scale_rb/runtime_spec' require 'scale_rb/hasher' require 'scale_rb/storage_helper' diff --git a/lib/scale_rb/portable_registry.rb b/lib/scale_rb/portable_registry.rb index f3e28e4..33fa048 100644 --- a/lib/scale_rb/portable_registry.rb +++ b/lib/scale_rb/portable_registry.rb @@ -24,6 +24,10 @@ def inspect "a_portable_registry" end + def to_s + "a_portable_registry" + end + def add_type(type) @types << type end diff --git a/lib/scale_rb/runtime_spec.rb b/lib/scale_rb/runtime_spec.rb new file mode 100644 index 0000000..153dcc7 --- /dev/null +++ b/lib/scale_rb/runtime_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative './metadata/metadata' + +module ScaleRb + class RuntimeSpec + attr_reader :metadata, :version + + def initialize(hex) + @metadata_prefixed, = ScaleRb::Codec.decode( + 'MetadataPrefixed', + ScaleRb::Utils.hex_to_u8a(hex), + ScaleRb::OldRegistry.new(Metadata::TYPES) + ) + metadata = @metadata_prefixed[:metadata] + @version = metadata.keys.first + @metadata = metadata[@version] + end + + def portable_registry + @portable_registry ||= + case @version + when :V14 + ScaleRb::PortableRegistry.new(@metadata.dig(:lookup, :types)) + else + raise NotImplementedError, @version + end + end + + def pallet(pallet_name) + @metadata._get(:pallets).find do |p| + p._get(:name) == pallet_name + end + end + end +end diff --git a/spec/runtime_spec_spec.rb b/spec/runtime_spec_spec.rb new file mode 100644 index 0000000..2f9aaf3 --- /dev/null +++ b/spec/runtime_spec_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'scale_rb' + +RSpec.describe ScaleRb::RuntimeSpec do + let(:hex) { File.read('spec/assets/substrate-metadata-v14-hex').strip } + let(:runtime_spec) { ScaleRb::RuntimeSpec.new(hex) } + + it 'have version' do + expect(runtime_spec.version).to eq(:V14) + end + + it 'have portable registry' do + expect(runtime_spec.portable_registry.types.length).to eq(696) + end +end From f152d764f77f35c3e43e79f7fe42ac8abc9c4c1b Mon Sep 17 00:00:00 2001 From: Aki Wu Date: Mon, 21 Oct 2024 21:45:00 +0800 Subject: [PATCH 3/4] update --- bin/console | 0 exe/metadata | 2 +- lib/scale_rb.rb | 2 +- lib/scale_rb/call_helper.rb | 10 +- lib/scale_rb/client/client_ext.rb | 8 +- lib/scale_rb/metadata/metadata.rb | 95 +++++++++--------- lib/scale_rb/runtime_spec.rb | 36 ------- lib/scale_rb/runtime_types.rb | 67 +++++++++++++ lib/scale_rb/storage_helper.rb | 4 +- spec/call_spec.rb | 10 +- spec/extrinsic_helper_spec.rb | 155 ------------------------------ spec/metadata_spec.rb | 103 ++------------------ spec/runtime_spec_spec.rb | 16 --- spec/runtime_types_spec.rb | 38 ++++++++ spec/storage_helper_spec.rb | 2 +- 15 files changed, 174 insertions(+), 374 deletions(-) mode change 100644 => 100755 bin/console delete mode 100644 lib/scale_rb/runtime_spec.rb create mode 100644 lib/scale_rb/runtime_types.rb delete mode 100644 spec/extrinsic_helper_spec.rb delete mode 100644 spec/runtime_spec_spec.rb create mode 100644 spec/runtime_types_spec.rb diff --git a/bin/console b/bin/console old mode 100644 new mode 100755 diff --git a/exe/metadata b/exe/metadata index 3724024..242d768 100755 --- a/exe/metadata +++ b/exe/metadata @@ -35,5 +35,5 @@ else end metadata = client.get_metadata(block_hash) - puts JSON.pretty_generate(metadata) + puts metadata.to_json end diff --git a/lib/scale_rb.rb b/lib/scale_rb.rb index 0180f9e..93545d3 100644 --- a/lib/scale_rb.rb +++ b/lib/scale_rb.rb @@ -25,7 +25,7 @@ def debug(key, value) require 'scale_rb/codec' require 'scale_rb/metadata/metadata' -require 'scale_rb/runtime_spec' +require 'scale_rb/runtime_types' require 'scale_rb/hasher' require 'scale_rb/storage_helper' diff --git a/lib/scale_rb/call_helper.rb b/lib/scale_rb/call_helper.rb index 4b7211b..deeb83d 100644 --- a/lib/scale_rb/call_helper.rb +++ b/lib/scale_rb/call_helper.rb @@ -9,7 +9,7 @@ module CallHelper # "0x05000a1287977578f888bdc1c7627781af1cc000e6ab1300004c31b8d9a798"._to_bytes def self.decode_call(callbytes, metadata) pallet_index = callbytes[0] - pallet = Metadata.get_module_by_index(pallet_index, metadata) + pallet = metadata.pallet_by_index(pallet_index) # Remove the pallet_index # The callbytes we used below should not contain the pallet index. @@ -20,7 +20,7 @@ def self.decode_call(callbytes, metadata) decoded = Codec.decode( calls_type_id, callbytes_without_pallet_index, - Metadata.build_registry(metadata) + metadata.build_registry )&.first { @@ -34,9 +34,9 @@ def self.decode_call(callbytes, metadata) # {:pallet_name=>"Deposit", :call_name=>"claim", :call=>:claim]} # {: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}}]} def self.encode_call(call, metadata) - calls_type_id = Metadata.get_calls_type_id(call[:pallet_name], metadata) - pallet_index = Metadata.get_module(call[:pallet_name], metadata)._get(:index) - [pallet_index] + Codec.encode(calls_type_id, call[:call], Metadata.build_registry(metadata)) + calls_type_id = metadata.calls_type_id(call[:pallet_name]) + pallet_index = metadata.pallet(call[:pallet_name])[:index] + [pallet_index] + Codec.encode(calls_type_id, call[:call], metadata.build_registry) end end end diff --git a/lib/scale_rb/client/client_ext.rb b/lib/scale_rb/client/client_ext.rb index 9dfac87..df20d00 100644 --- a/lib/scale_rb/client/client_ext.rb +++ b/lib/scale_rb/client/client_ext.rb @@ -5,7 +5,7 @@ module ClientExt def get_metadata(block_hash = nil) block_hash ||= chain_getHead metadata_hex = state_getMetadata(block_hash) - Metadata.decode_metadata(metadata_hex) + Metadata::Metadata.new(metadata_hex) end # Get decoded storage at block_hash @@ -134,10 +134,8 @@ def get_storage1(block_hash, pallet_name, item_name, key, value, registry) def get_storage2(block_hash, pallet_name, item_name, params, metadata) raise 'Metadata should not be nil' if metadata.nil? - registry = Metadata.build_registry(metadata) - item = Metadata.get_storage_item( - pallet_name, item_name, metadata - ) + registry = metadata.build_registry + item = metadata.storage(pallet_name, item_name) raise "No such storage item: `#{pallet_name}`.`#{item_name}`" if item.nil? modifier = item._get(:modifier) # Default | Optional diff --git a/lib/scale_rb/metadata/metadata.rb b/lib/scale_rb/metadata/metadata.rb index cd4f9cb..774e1bb 100644 --- a/lib/scale_rb/metadata/metadata.rb +++ b/lib/scale_rb/metadata/metadata.rb @@ -11,73 +11,74 @@ # https://github.com/paritytech/frame-metadata/blob/main/frame-metadata/src/lib.rs#L85 module ScaleRb module Metadata - class << self - def decode_metadata(hex) - bytes = ScaleRb::Utils.hex_to_u8a(hex) - - registry = ScaleRb::OldRegistry.new TYPES - metadata, = ScaleRb::Codec.decode('MetadataPrefixed', bytes, registry) - metadata + class Metadata + attr_reader :magic_number, :version, :metadata, :types + + def initialize(metadata_prefixed) + @metadata_prefixed = metadata_prefixed + @magic_number = @metadata_prefixed[:magicNumber] + metadata = @metadata_prefixed[:metadata] + @version = metadata.keys.first + raise "Unsupported metadata version: #{@version}" unless @version == :V14 + + @metadata = metadata[@version] + @types = @metadata.dig(:lookup, :types) end - def build_registry(metadata_prefixed) - MetadataV14.build_registry(metadata_prefixed) + def self.from_hex(hex) + metadata_prefixed, = ScaleRb::Codec.decode('MetadataPrefixed', Utils.hex_to_u8a(hex), OldRegistry.new(TYPES)) + Metadata.new(metadata_prefixed) end - def get_module(pallet_name, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first - raise NotImplementedError, version unless %i[V14].include?(version) - - Metadata.const_get("Metadata#{version.upcase}").get_module(pallet_name, metadata_prefixed) + def self.from_json(str) + metadata_prefixed = JSON.parse(str, symbolize_names: true) + Metadata.new(metadata_prefixed) end - def get_module_by_index(pallet_index, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first.to_sym - raise NotImplementedError, version unless %i[V14].include?(version) - - Metadata.const_get("Metadata#{version.upcase}").get_module_by_index(pallet_index, metadata_prefixed) + def to_json(*_args) + JSON.pretty_generate(@metadata_prefixed) end - def get_storage_item(pallet_name, item_name, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first.to_sym - raise NotImplementedError, version unless %i[V14].include?(version) - - Metadata.const_get("Metadata#{version.upcase}").get_storage_item(pallet_name, item_name, metadata_prefixed) + def build_registry + ScaleRb::PortableRegistry.new(@metadata.dig(:lookup, :types)) end - def get_calls_type(pallet_name, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first.to_sym - raise NotImplementedError, version unless %i[V14].include?(version) + def pallet(pallet_name) + @metadata[:pallets].find do |pallet| + pallet[:name] == pallet_name + end + end - Metadata.const_get("Metadata#{version.upcase}").get_calls_type(pallet_name, metadata_prefixed) + def pallet_by_index(pallet_index) + @metadata[:pallets].find do |pallet| + pallet[:index] == pallet_index + end end - def get_calls_type_id(pallet_name, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first.to_sym - raise NotImplementedError, version unless %i[V14].include?(version) + def storage(pallet_name, item_name) + pallet = pallet(pallet_name) + raise "Pallet `#{pallet_name}` not found" if pallet.nil? - Metadata.const_get("Metadata#{version.upcase}").get_calls_type_id(pallet_name, metadata_prefixed) + pallet.dig(:storage, :items).find do |item| + item[:name] == item_name + end end - def get_call_type(pallet_name, call_name, metadata_prefixed) - metadata = Utils.get(metadata_prefixed, :metadata) - version = metadata.keys.first.to_sym - raise NotImplementedError, version unless %i[V14].include?(version) + def calls_type_id(pallet_name) + pallet = pallet(pallet_name) + raise "Pallet `#{pallet_name}` not found" if pallet.nil? - Metadata.const_get("Metadata#{version.upcase}").get_call_type(pallet_name, call_name, metadata_prefixed) + pallet.dig(:calls, :type) end - def signature_type(metadata_prefixed) - MetadataV14.signature_type(metadata_prefixed) - end + def call(pallet_name, call_name) + calls_type_id = calls_type_id(pallet_name) + calls_type = @types[calls_type_id] + raise 'Calls type is not correct' if calls_type.nil? - def signed_extensions(metadata_prefixed) - MetadataV14.signed_extensions(metadata_prefixed) + calls_type.dig(:type, :def, :variant, :variants).find do |variant| + variant[:name].downcase == call_name.downcase + end end end diff --git a/lib/scale_rb/runtime_spec.rb b/lib/scale_rb/runtime_spec.rb deleted file mode 100644 index 153dcc7..0000000 --- a/lib/scale_rb/runtime_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative './metadata/metadata' - -module ScaleRb - class RuntimeSpec - attr_reader :metadata, :version - - def initialize(hex) - @metadata_prefixed, = ScaleRb::Codec.decode( - 'MetadataPrefixed', - ScaleRb::Utils.hex_to_u8a(hex), - ScaleRb::OldRegistry.new(Metadata::TYPES) - ) - metadata = @metadata_prefixed[:metadata] - @version = metadata.keys.first - @metadata = metadata[@version] - end - - def portable_registry - @portable_registry ||= - case @version - when :V14 - ScaleRb::PortableRegistry.new(@metadata.dig(:lookup, :types)) - else - raise NotImplementedError, @version - end - end - - def pallet(pallet_name) - @metadata._get(:pallets).find do |p| - p._get(:name) == pallet_name - end - end - end -end diff --git a/lib/scale_rb/runtime_types.rb b/lib/scale_rb/runtime_types.rb new file mode 100644 index 0000000..f80be77 --- /dev/null +++ b/lib/scale_rb/runtime_types.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require_relative './metadata/metadata' + +module ScaleRb + class RuntimeTypes + attr_reader :metadata, :version + + def initialize(hex) + @metadata_prefixed, = ScaleRb::Codec.decode( + 'MetadataPrefixed', + ScaleRb::Utils.hex_to_u8a(hex), + ScaleRb::OldRegistry.new(Metadata::TYPES) + ) + metadata = @metadata_prefixed[:metadata] + @version = metadata.keys.first + @metadata = metadata[@version] + raise "Unsupported metadata version: #{@version}" unless @version == :V14 + end + + def registry + @registry ||= + ScaleRb::PortableRegistry.new(@metadata.dig(:lookup, :types)) + end + + def pallet(pallet_name) + @metadata[:pallets].find do |pallet| + pallet[:name] == pallet_name + end + end + + def pallet_by_index(pallet_index) + @metadata[:pallets].find do |pallet| + pallet[:index] == pallet_index + end + end + + def storage(pallet_name, item_name) + pallet = pallet(pallet_name) + raise "Pallet `#{pallet_name}` not found" if pallet.nil? + + pallet.dig(:storage, :items).find do |item| + item[:name] == item_name + end + end + + # example: + # #]>> + def call(pallet_name, call_name) + calls_type = registry[calls_type_id(pallet_name)] + raise 'Calls type is not correct' unless calls_type.is_a?(ScaleRb::Types::VariantType) + + calls_type.variants.find do |variant| + variant.name == call_name.to_sym + end + end + + private + + def calls_type_id(pallet_name) + pallet = pallet(pallet_name) + raise "Pallet `#{pallet_name}` not found" if pallet.nil? + + pallet.dig(:calls, :type) + end + end +end diff --git a/lib/scale_rb/storage_helper.rb b/lib/scale_rb/storage_helper.rb index 03bb628..d6ab5f7 100644 --- a/lib/scale_rb/storage_helper.rb +++ b/lib/scale_rb/storage_helper.rb @@ -69,8 +69,8 @@ def decode_storage2(data, storage_item, registry) end def decode_storage3(data, pallet_name, item_name, metadata) - registry = Metadata.build_registry(metadata) - storage_item = Metadata.get_storage_item(pallet_name, item_name, metadata) + registry = metadata.build_registry + storage_item = metadata.storage(pallet_name, item_name) decode_storage2(data, storage_item, registry) end end diff --git a/spec/call_spec.rb b/spec/call_spec.rb index effb310..ffe0659 100644 --- a/spec/call_spec.rb +++ b/spec/call_spec.rb @@ -6,10 +6,7 @@ module ScaleRb RSpec.describe CallHelper do it 'can decode call' do - metadata = JSON.parse( - File.read('./spec/assets/pangolin2.json'), - symbolize_names: true - ) + metadata = Metadata::Metadata.from_json(File.read('./spec/assets/pangolin2.json')) callbytes = Utils.hex_to_u8a('0x0901') decoded = CallHelper.decode_call(callbytes, metadata) @@ -57,10 +54,7 @@ module ScaleRb end it 'can encode call' do - metadata = JSON.parse( - File.read('./spec/assets/pangolin2.json'), - symbolize_names: true - ) + metadata = Metadata::Metadata.from_json(File.read('./spec/assets/pangolin2.json')) call = { pallet_name: 'Deposit', call_name: 'claim', call: :claim } encoded = CallHelper.encode_call(call, metadata) diff --git a/spec/extrinsic_helper_spec.rb b/spec/extrinsic_helper_spec.rb deleted file mode 100644 index 88957ef..0000000 --- a/spec/extrinsic_helper_spec.rb +++ /dev/null @@ -1,155 +0,0 @@ -# frozen_string_literal: true - -require 'scale_rb' -require 'json' - -module ScaleRb - RSpec.describe StorageHelper do - before(:all) do - data = JSON.parse(File.open(File.join(__dir__, 'assets', 'darwinia-types.json')).read) - @registry = ScaleRb::PortableRegistry.new(data) - end - - it 'can encode storage key without param' do - storage_key = StorageHelper.encode_storage_key('System', 'EventCount') - storage_key = Utils.u8a_to_hex(storage_key) - expect(storage_key).to eql('0x26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850') - end - - it 'can encode storage key with one param' do - # account_id - key = { - value: [Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')], - type: 0, - hashers: ['Blake2_128Concat'] - } - - storage_key = StorageHelper.encode_storage_key( - 'System', - 'Account', - key, - @registry - ) - expect = Utils.hex_to_u8a('0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94bab0fcfc536fa263f3b241cd32f76a8724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641') - expect(storage_key).to eql(expect) - end - - it 'can encode storage key with two param(same hasher)' do - storage_key = StorageHelper.encode_storage_key( - 'ImOnline', - 'AuthoredBlocks', - { - value: [123, Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')], # U32, AccountId - type: 291, # 291 => [4, 0] - hashers: %w[Twox64Concat Twox64Concat] - }, - @registry - ) - expect = Utils.hex_to_u8a('0x2b06af9719ac64d755623cda8ddd9b94b1c371ded9e9c565e89ba783c4d5f5f92a9a1a82315e68fd7b000000a2c377ff1d6261f6724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641') - expect(storage_key).to eql(expect) - end - - it 'can encode storage key with two param(different hasher)' do - storage_key = StorageHelper.encode_storage_key( - 'Multisig', - 'Multisigs', - { - value: [ - Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641'), # AccountId - Utils.hex_to_u8a('0x0101010101010101010101010101010101010101010101010101010101010101') # [U8; 32] array - ], - type: 582, # 582 => [0, 1] - hashers: %w[Twox64Concat Blake2_128Concat] - }, - @registry - ) - expect = Utils.hex_to_u8a('0x7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8da2c377ff1d6261f6724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641c035f853fcd0f0589e30c9e2dc1a0f570101010101010101010101010101010101010101010101010101010101010101') - expect(storage_key).to eql(expect) - end - - it 'can decode system events' do - metadata = JSON.parse(File.open(File.join(__dir__, 'assets', 'darwinia-metadata.1243.json')).read) - data = '0x1800000000000000585f8f09000000000200000001000000040964766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996404b4c00000000000000000000000000000001000000040764766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996623116000000000000000000000000000000010000002f009c266c48f07121181d8424768f0ded0170cc63a6044c6030db06afe5c2251138fd7b0c3aef3876f9f60cecfae80a2e3b9cdd3b6d5d810200000000000000000000000000000000000000000000000000000000004b32200000000000000000000000000000000000000000000000000000000000051210db0ddcce0c5a3514e4396b69edac100b112deb966d7a6ee4ab8423edfc779b58f9ed9f96d0c7ba91d11970ea62f7648a7ba440ebacdcc1023c3ba310280cc7239edd250ff23d036d7d9ffc03377346814463d22f3e50fac3179f49a9c30e642c00000100000030002eabe5c6818731e282b80de1a03f8190426e0dd99c266c48f07121181d8424768f0ded0170cc63a6757a2695ae238d39120f2897d6e555b144c90f62ef457009bd83afc1dafc2e6b0000000001000000000080bf490521000000000000' - storage = StorageHelper.decode_storage3(data, 'System', 'Events', metadata) - expect = - [ - { - phase: { ApplyExtrinsic: 0 }, - event: { - System: { - ExtrinsicSuccess: { - weight: 160_391_000, - class: :Mandatory, - pays_fee: :Yes - } - } - }, - topics: [] - }, - { - phase: { ApplyExtrinsic: 1 }, - event: { - Balances: { - Slashed: { - who: ScaleRb::Utils.hex_to_u8a('0x64766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996'), - amount: 5_000_000 - } - } - }, - topics: [] - }, - { - phase: { ApplyExtrinsic: 1 }, - event: { - Balances: { - Deposit: { - who: ScaleRb::Utils.hex_to_u8a('0x64766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996'), - amount: 1_454_434 - } - } - }, - topics: [] - }, - { - phase: { ApplyExtrinsic: 1 }, - event: { - EVM: { - Log: { - log: { - address: ScaleRb::Utils.hex_to_u8a('0x9c266c48f07121181d8424768f0ded0170cc63a6'), - topics: [ScaleRb::Utils.hex_to_u8a('0x4c6030db06afe5c2251138fd7b0c3aef3876f9f60cecfae80a2e3b9cdd3b6d5d')], - data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, - 50, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 18, 16, 219, 13, 220, 206, 12, 90, 53, 20, 228, 57, 107, 105, 237, 172, 16, 11, 17, 45, 235, 150, 109, 122, 110, 228, 171, 132, 35, 237, 252, 119, 155, 88, 249, 237, 159, 150, 208, 199, 186, 145, 209, 25, 112, 234, 98, 247, 100, 138, 123, 164, 64, 235, 172, 220, 193, 2, 60, 59, 163, 16, 40, 12, 199, 35, 158, 221, 37, 15, 242, 61, 3, 109, 125, 159, 252, 3, 55, 115, 70, 129, 68, 99, 210, 47, 62, 80, 250, 195, 23, 159, 73, 169, 195, 14, 100, 44] - } - } - } - }, - topics: [] - }, - { - phase: { ApplyExtrinsic: 1 }, - event: { - Ethereum: { - Executed: { - from: ScaleRb::Utils.hex_to_u8a('0x2eabe5c6818731e282b80de1a03f8190426e0dd9'), - to: ScaleRb::Utils.hex_to_u8a('0x9c266c48f07121181d8424768f0ded0170cc63a6'), - transaction_hash: ScaleRb::Utils.hex_to_u8a('0x757a2695ae238d39120f2897d6e555b144c90f62ef457009bd83afc1dafc2e6b'), - exit_reason: { - Succeed: :Stopped - } - } - } - }, - topics: [] - }, - { - phase: { ApplyExtrinsic: 1 }, - event: { System: { ExtrinsicSuccess: { weight: 141_822_640_000, class: :Normal, pays_fee: :Yes } } }, - topics: [] - } - ] - - expect(storage).to eql(expect) - end - end -end diff --git a/spec/metadata_spec.rb b/spec/metadata_spec.rb index 22708ad..79fd2fd 100644 --- a/spec/metadata_spec.rb +++ b/spec/metadata_spec.rb @@ -12,17 +12,17 @@ module ScaleRb before(:all) do hex = File.read('./spec/assets/substrate-metadata-v14-hex').strip time = Benchmark.measure do - @metadata = ScaleRb::Metadata.decode_metadata(hex) + @metadata = ScaleRb::Metadata::Metadata.from_hex(hex) end puts "Decoding metadata v14: #{time.real / 60} minutes" end it 'can decode metadata v14' do - expect(@metadata[:magicNumber]).to eq(1_635_018_093) + expect(@metadata.magic_number).to eq(1_635_018_093) end it 'can get storage item from metadata v14' do - storage_item = ScaleRb::Metadata.get_storage_item('System', 'BlockHash', @metadata) + storage_item = @metadata.storage('System', 'BlockHash') expect(storage_item).to eql( { name: 'BlockHash', @@ -36,7 +36,7 @@ module ScaleRb end it 'can get module by name' do - system_module = ScaleRb::Metadata.get_module('System', @metadata) + system_module = @metadata.pallet('System') expect(system_module.keys).to eql(%i[name storage calls events constants errors index]) expect(system_module[:name]).to eql('System') expect(system_module[:calls]).to eql({ type: 141 }) @@ -44,98 +44,12 @@ module ScaleRb end it 'can get module by index' do - system_module = ScaleRb::Metadata.get_module_by_index(0, @metadata) + system_module = @metadata.pallet_by_index(0) expect(system_module.keys).to eql(%i[name storage calls events constants errors index]) end - it 'can get calls type' do - calls_type = ScaleRb::Metadata.get_calls_type('System', @metadata) - expect(calls_type).to eql( - { - id: 141, - type: { - path: %w[frame_system pallet Call], - params: [{ name: 'T', type: nil }], - def: { - variant: { - variants: [ - { - name: 'fill_block', - fields: [{ name: 'ratio', type: 45, typeName: 'Perbill', docs: [] }], - index: 0, - docs: ['A dispatch that will fill the block weight up to the given ratio.'] - }, - { - name: 'remark', - fields: [{ name: 'remark', type: 12, typeName: 'Vec', docs: [] }], - index: 1, - docs: [ - 'Make some on-chain remark.', - '', - '# ', - '- `O(1)`', - '# ' - ] - }, - { - name: 'set_heap_pages', - fields: [{ name: 'pages', type: 10, typeName: 'u64', docs: [] }], - index: 2, - docs: ["Set the number of pages in the WebAssembly environment's heap."] - }, - { - name: 'set_code', - fields: [{ name: 'code', type: 12, typeName: 'Vec', docs: [] }], - index: 3, - docs: [ - 'Set the new runtime code.', - '', - '# ', - '- `O(C + S)` where `C` length of `code` and `S` complexity of `can_set_code`', - '- 1 call to `can_set_code`: `O(S)` (calls `sp_io::misc::runtime_version` which is', - ' expensive).', - '- 1 storage write (codec `O(C)`).', - '- 1 digest item.', - '- 1 event.', - 'The weight of this function is dependent on the runtime, but generally this is very', - 'expensive. We will treat this as a full block.', - '# ' - ] - }, - { - name: 'set_code_without_checks', - fields: [{ name: 'code', type: 12, typeName: 'Vec', docs: [] }], - index: 4, - docs: [ - 'Set the new runtime code without doing any checks of the given `code`.', - '', - '# ', - '- `O(C)` where `C` length of `code`', - '- 1 storage write (codec `O(C)`).', - '- 1 digest item.', - '- 1 event.', - 'The weight of this function is dependent on the runtime. We will treat this as a full', 'block. # ' - ] - }, - { name: 'set_storage', fields: [{ name: 'items', type: 142, typeName: 'Vec', docs: [] }], - index: 5, docs: ['Set some items of storage.'] }, - { name: 'kill_storage', fields: [{ name: 'keys', type: 144, typeName: 'Vec', docs: [] }], - index: 6, docs: ['Kill some items from storage.'] }, - { name: 'kill_prefix', - fields: [{ name: 'prefix', type: 12, typeName: 'Key', docs: [] }, { name: 'subkeys', type: 4, typeName: 'u32', docs: [] }], index: 7, docs: ['Kill all storage items with a key that starts with the given prefix.', '', '**NOTE:** We rely on the Root origin to provide us the number of subkeys under', 'the prefix we are removing to accurately calculate the weight of this function.'] }, - { name: 'remark_with_event', fields: [{ name: 'remark', type: 12, typeName: 'Vec', docs: [] }], - index: 8, docs: ['Make some on-chain remark and emit event.'] } - ] - } - }, - docs: ['Contains one variant per dispatchable that can be called by an extrinsic.'] - } - } - ) - end - it 'can get call type' do - call_type = ScaleRb::Metadata.get_call_type('System', 'remark', @metadata) + call_type = @metadata.call('System', 'remark') expect(call_type).to eql( { name: 'remark', @@ -152,11 +66,6 @@ module ScaleRb ) end - it 'can build registry from its lookup' do - registry = ScaleRb::Metadata.build_registry(@metadata) - expect(registry.types.length).to eq(696) - end - # it 'can get signature type' do # expect = [ # { identifier: 'CheckNonZeroSender', type: 686, additionalSigned: 31 }, diff --git a/spec/runtime_spec_spec.rb b/spec/runtime_spec_spec.rb deleted file mode 100644 index 2f9aaf3..0000000 --- a/spec/runtime_spec_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require 'scale_rb' - -RSpec.describe ScaleRb::RuntimeSpec do - let(:hex) { File.read('spec/assets/substrate-metadata-v14-hex').strip } - let(:runtime_spec) { ScaleRb::RuntimeSpec.new(hex) } - - it 'have version' do - expect(runtime_spec.version).to eq(:V14) - end - - it 'have portable registry' do - expect(runtime_spec.portable_registry.types.length).to eq(696) - end -end diff --git a/spec/runtime_types_spec.rb b/spec/runtime_types_spec.rb new file mode 100644 index 0000000..fe1a902 --- /dev/null +++ b/spec/runtime_types_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'scale_rb' + +RSpec.describe ScaleRb::RuntimeTypes do + let(:hex) { File.read('spec/assets/substrate-metadata-v14-hex').strip } + let(:runtime_types) { ScaleRb::RuntimeTypes.new(hex) } + + it 'will raise error if metadata version is not v14' do + hex_v13 = File.read('spec/assets/substrate-metadata-v13-hex').strip + expect { ScaleRb::RuntimeTypes.new(hex_v13) }.to raise_error(RuntimeError) + end + + it 'have version' do + expect(runtime_types.version).to eq(:V14) + end + + it 'have portable registry' do + expect(runtime_types.registry.types.length).to eq(696) + end + + it 'can get a pallet' do + expect(runtime_types.pallet('System')).not_to be_nil + end + + it 'can get a pallet by index' do + expect(runtime_types.pallet_by_index(0)).not_to be_nil + end + + it 'can get a storage' do + puts runtime_types.storage('System', 'BlockHash') + expect(runtime_types.storage('System', 'BlockHash')).not_to be_nil + end + + it 'can get a call' do + expect(runtime_types.call_type_id('System', 'remark')).to eql(1) + end +end diff --git a/spec/storage_helper_spec.rb b/spec/storage_helper_spec.rb index 88957ef..9237ff5 100644 --- a/spec/storage_helper_spec.rb +++ b/spec/storage_helper_spec.rb @@ -68,7 +68,7 @@ module ScaleRb end it 'can decode system events' do - metadata = JSON.parse(File.open(File.join(__dir__, 'assets', 'darwinia-metadata.1243.json')).read) + metadata = Metadata::Metadata.from_json(File.read('./spec/assets/darwinia-metadata.1243.json')) data = '0x1800000000000000585f8f09000000000200000001000000040964766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996404b4c00000000000000000000000000000001000000040764766d3a000000000000002eabe5c6818731e282b80de1a03f8190426e0dd996623116000000000000000000000000000000010000002f009c266c48f07121181d8424768f0ded0170cc63a6044c6030db06afe5c2251138fd7b0c3aef3876f9f60cecfae80a2e3b9cdd3b6d5d810200000000000000000000000000000000000000000000000000000000004b32200000000000000000000000000000000000000000000000000000000000051210db0ddcce0c5a3514e4396b69edac100b112deb966d7a6ee4ab8423edfc779b58f9ed9f96d0c7ba91d11970ea62f7648a7ba440ebacdcc1023c3ba310280cc7239edd250ff23d036d7d9ffc03377346814463d22f3e50fac3179f49a9c30e642c00000100000030002eabe5c6818731e282b80de1a03f8190426e0dd99c266c48f07121181d8424768f0ded0170cc63a6757a2695ae238d39120f2897d6e555b144c90f62ef457009bd83afc1dafc2e6b0000000001000000000080bf490521000000000000' storage = StorageHelper.decode_storage3(data, 'System', 'Events', metadata) expect = From fa539eed0636fd6821a05b4427b652bf4788eeee Mon Sep 17 00:00:00 2001 From: Aki Wu Date: Mon, 21 Oct 2024 21:49:04 +0800 Subject: [PATCH 4/4] fix --- lib/scale_rb/client/client_ext.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scale_rb/client/client_ext.rb b/lib/scale_rb/client/client_ext.rb index df20d00..98216ea 100644 --- a/lib/scale_rb/client/client_ext.rb +++ b/lib/scale_rb/client/client_ext.rb @@ -5,7 +5,7 @@ module ClientExt def get_metadata(block_hash = nil) block_hash ||= chain_getHead metadata_hex = state_getMetadata(block_hash) - Metadata::Metadata.new(metadata_hex) + Metadata::Metadata.from_hex(metadata_hex) end # Get decoded storage at block_hash