class Eth::Abi::Type

Provides a class to handle and parse common ABI types.

def self.size_type

Returns:
  • (Eth::Abi::Type) - a uint256 size type.
def self.size_type
  @size_type ||= new("uint", 256, [])
end

def ==(another_type)

Returns:
  • (Boolean) - true if all attributes match.

Parameters:
  • another_type (Eth::Abi::Type) -- another type to be compared.
def ==(another_type)
  base_type == another_type.base_type and
    sub_type == another_type.sub_type and
    dimensions == another_type.dimensions
end

def dynamic?

Returns:
  • (Boolean) - true if array is of dynamic size.
def dynamic?
  size.nil?
end

def initialize(base_type, sub_type, dimensions, components = nil, component_name = nil)

Returns:
  • (Eth::Abi::Type) - an ABI type object.

Parameters:
  • component_name (String) -- the tuple component's name.
  • components (Array) -- the components attribute.
  • dimensions (Array) -- the dimension attribute.
  • sub_type (String) -- the sub-type attribute.
  • base_type (String) -- the base-type attribute.
def initialize(base_type, sub_type, dimensions, components = nil, component_name = nil)
  sub_type = sub_type.to_s
  @base_type = base_type
  @sub_type = sub_type
  @dimensions = dimensions
  @components = components
  @name = component_name
end

def nested_sub

Returns:
  • (Eth::Abi::Type) - nested sub-type.
def nested_sub
  @nested_sub ||= self.class.new(base_type, sub_type, dimensions[0...-1], components, name)
end

def parse(type, components = nil, component_name = nil)

Raises:
  • (ParseError) - if it fails to parse the type.

Returns:
  • (Eth::Abi::Type) - a parsed Type object.

Parameters:
  • component_name (String) -- the tuple component's name.
  • components (Array) -- the components attribute.
  • type (String) -- a common Solidity type.
def parse(type, components = nil, component_name = nil)
  return type if type.is_a?(Type)
  _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
  # type dimension can only be numeric
  dims = dimension.scan(/\[[0-9]*\]/)
  raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
  # enforce base types
  validate_base_type base_type, sub_type
  # return a new Type (using konstructor)
  sub_type = sub_type.to_s
  @base_type = base_type
  @sub_type = sub_type
  @dimensions = dims.map { |x| x[1...-1].to_i }
  @components = components.map { |component| Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } unless components.nil?
  @name = component_name
end

def size

Returns:
  • (Integer) - the size of the type; or nil if not available.
def size
  s = nil
  if dimensions.empty?
    if !(["string", "bytes", "tuple"].include?(base_type) and sub_type.empty?)
      s = 32
    elsif base_type == "tuple" && components.none?(&:dynamic?)
      s = components.sum(&:size)
    end
  elsif dimensions.last != 0 && !nested_sub.dynamic?
    s = dimensions.last * nested_sub.size
  end
  @size ||= s
end

def to_s

Returns:
  • (String) - the type string.
def to_s
  if base_type == "tuple"
    "(" + components.map(&:to_s).join(",") + ")" + (dimensions.size > 0 ? dimensions.map { |x| "[#{x == 0 ? "" : x}]" }.join : "")
  elsif dimensions.empty?
    if %w[string bytes].include?(base_type) && sub_type.empty?
      base_type
    else
      "#{base_type}#{sub_type}"
    end
  else
    "#{base_type}#{sub_type}#{dimensions.map { |x| "[#{x == 0 ? "" : x}]" }.join}"
  end
end

def validate_base_type(base_type, sub_type)

Validates all known base types and raises if an issue occurs.
def validate_base_type(base_type, sub_type)
  case base_type
  when "string"
    # string can not have any suffix
    raise ParseError, "String type must have no suffix or numerical suffix" unless sub_type.empty?
  when "bytes"
    # bytes can be no longer than 32 bytes
    raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub_type.empty? || sub_type.to_i <= 32
  when "tuple"
    # tuples can not have any suffix
    raise ParseError, "Tuple type must have no suffix or numerical suffix" unless sub_type.empty?
  when "uint", "int"
    # integers must have a numerical suffix
    raise ParseError, "Integer type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
    # integer size must be valid
    size = sub_type.to_i
    raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
    raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
  when "ureal", "real", "fixed", "ufixed"
    # floats must have valid dimensional suffix
    raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub_type =~ /\A[0-9]+x[0-9]+\z/
    high, low = sub_type.split("x").map(&:to_i)
    total = high + low
    raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
    raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
  when "hash"
    # hashs must have numerical suffix
    raise ParseError, "Hash type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
  when "address"
    # addresses cannot have any suffix
    raise ParseError, "Address cannot have suffix" unless sub_type.empty?
  when "bool"
    # booleans cannot have any suffix
    raise ParseError, "Bool cannot have suffix" unless sub_type.empty?
  else
    # we cannot parse arbitrary types such as 'decimal' or 'hex'
    raise ParseError, "Unknown base type"
  end
end