module Eth::Abi

def decode(types, data)

Returns:
  • (Array) - the decoded ABI data.

Parameters:
  • data (String) -- ABI data to be decoded.
  • types (Array) -- the ABI to be decoded.
def decode(types, data)
  # accept hex abi but decode it first
  data = Util.hex_to_bin data if Util.is_hex? data
  # parse all types
  parsed_types = types.map { |t| Type.parse(t) }
  # prepare output data
  outputs = [nil] * types.size
  start_positions = [nil] * types.size + [data.size]
  pos = 0
  parsed_types.each_with_index do |t, i|
    if t.is_dynamic?
      # record start position for dynamic type
      start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32])
      j = i - 1
      while j >= 0 and start_positions[j].nil?
        start_positions[j] = start_positions[i]
        j -= 1
      end
      pos += 32
    else
      # get data directly for static types
      outputs[i] = data[pos, t.size]
      pos += t.size
    end
  end
  # add start position equal the length of the entire data
  j = types.size - 1
  while j >= 0 and start_positions[j].nil?
    start_positions[j] = start_positions[types.size]
    j -= 1
  end
  raise DecodingError, "Not enough data for head" unless pos <= data.size
  # add dynamic types
  parsed_types.each_with_index do |t, i|
    if t.is_dynamic?
      offset, next_offset = start_positions[i, 2]
      outputs[i] = data[offset...next_offset]
    end
  end
  # return the decoded ABI types and data
  return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) }
end

def decode_primitive_type(type, data)

Raises:
  • (DecodingError) - if decoding fails for type.

Returns:
  • (String) - the decoded data for the type.

Parameters:
  • data (String) -- encoded primitive type data string.
  • type (Eth::Abi::Type) -- type to be decoded.
def decode_primitive_type(type, data)
  case type.base_type
  when "address"
    # decoded address with 0x-prefix
    return "0x#{Util.bin_to_hex data[12..-1]}"
  when "string", "bytes"
    if type.sub_type.empty?
      size = Util.deserialize_big_endian_to_int data[0, 32]
      # decoded dynamic-sized array
      return data[32..-1][0, size]
    else
      # decoded static-sized array
      return data[0, type.sub_type.to_i]
    end
  when "hash"
    # decoded hash
    return data[(32 - type.sub_type.to_i), type.sub_type.to_i]
  when "uint"
    # decoded unsigned integer
    return Util.deserialize_big_endian_to_int data
  when "int"
    u = Util.deserialize_big_endian_to_int data
    i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u
    # decoded integer
    return i
  when "ureal", "ufixed"
    high, low = type.sub_type.split("x").map(&:to_i)
    # decoded unsigned fixed point numeric
    return Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
  when "real", "fixed"
    high, low = type.sub_type.split("x").map(&:to_i)
    u = Util.deserialize_big_endian_to_int data
    i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
    # decoded fixed point numeric
    return i * 1.0 / 2 ** low
  when "bool"
    # decoded boolean
    return data[-1] == BYTE_ONE
  else
    raise DecodingError, "Unknown primitive type: #{type.base_type}"
  end
end

def decode_type(type, arg)

Raises:
  • (DecodingError) - if decoding fails for type.

Returns:
  • (String) - the decoded data for the type.

Parameters:
  • arg (String) -- encoded type data string.
  • type (Eth::Abi::Type) -- type to be decoded.
def decode_type(type, arg)
  if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
    l = Util.deserialize_big_endian_to_int arg[0, 32]
    data = arg[32..-1]
    raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l)
    # decoded strings and bytes
    return data[0, l]
  elsif type.is_dynamic?
    l = Util.deserialize_big_endian_to_int arg[0, 32]
    nested_sub = type.nested_sub
    # ref https://github.com/ethereum/tests/issues/691
    raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
    # decoded dynamic-sized arrays
    return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
  elsif !type.dimensions.empty?
    l = type.dimensions.last[0]
    nested_sub = type.nested_sub
    # decoded static-size arrays
    return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
  else
    # decoded primitive types
    return decode_primitive_type type, arg
  end
end

def encode(types, args)

Returns:
  • (String) - the encoded ABI data.

Parameters:
  • args (Array) -- values to be ABI-encoded.
  • types (Array) -- types to be ABI-encoded.
def encode(types, args)
  # prase all types
  parsed_types = types.map { |t| Type.parse(t) }
  # prepare the "head"
  head_size = (0...args.size)
    .map { |i| parsed_types[i].size or 32 }
    .reduce(0, &:+)
  head, tail = "", ""
  # encode types and arguments
  args.each_with_index do |arg, i|
    if parsed_types[i].is_dynamic?
      head += encode_type Type.size_type, head_size + tail.size
      tail += encode_type parsed_types[i], arg
    else
      head += encode_type parsed_types[i], arg
    end
  end
  # return the encoded ABI blob
  return "#{head}#{tail}"
end

def encode_address(arg)

def encode_address(arg)
  if arg.is_a? Integer
    # address from integer
    return Util.zpad_int arg
  elsif arg.size == 20
    # address from encoded address
    return Util.zpad arg, 32
  elsif arg.size == 40
    # address from hexa-decimal address with 0x prefix
    return Util.zpad_hex arg
  elsif arg.size == 42 and arg[0, 2] == "0x"
    # address from hexa-decimal address
    return Util.zpad_hex arg[2..-1]
  else
    raise EncodingError, "Could not parse address: #{arg}"
  end
end

def encode_bool(arg)

def encode_bool(arg)
  raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass
  return Util.zpad_int(arg ? 1 : 0)
end

def encode_bytes(arg, type)

def encode_bytes(arg, type)
  raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String
  if type.sub_type.empty?
    size = Util.zpad_int arg.size
    padding = BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
    # variable length string/bytes
    return "#{size}#{arg}#{padding}"
  else
    raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i
    padding = BYTE_ZERO * (32 - arg.size)
    # fixed length string/bytes
    return "#{arg}#{padding}"
  end
end

def encode_fixed(arg, type)

def encode_fixed(arg, type)
  high, low = type.sub_type.split("x").map(&:to_i)
  raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
  i = (arg * 2 ** low).to_i
  return Util.zpad_int(i % 2 ** (high + low))
end

def encode_hash(arg, type)

def encode_hash(arg, type)
  size = type.sub_type.to_i
  raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32
  if arg.is_a? Integer
    # hash from integer
    return Util.zpad_int arg
  elsif arg.size == size
    # hash from encoded hash
    return Util.zpad arg, 32
  elsif arg.size == size * 2
    # hash from hexa-decimal hash
    return Util.zpad_hex arg
  else
    raise EncodingError, "Could not parse hash: #{arg}"
  end
end

def encode_int(arg, type)

def encode_int(arg, type)
  raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > INT_MAX or arg < INT_MIN
  real_size = type.sub_type.to_i
  i = arg.to_i
  raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1)
  return Util.zpad_int(i % 2 ** type.sub_type.to_i)
end

def encode_primitive_type(type, arg)

Raises:
  • (EncodingError) - if encoding fails for type.
  • (ValueOutOfBounds) - if value is out of bounds for type.
  • (EncodingError) - if value does not match type.

Returns:
  • (String) - the encoded primitive type.

Parameters:
  • arg (String, Number) -- value to be encoded.
  • type (Eth::Abi::Type) -- type to be encoded.
def encode_primitive_type(type, arg)
  case type.base_type
  when "uint"
    return encode_uint arg, type
  when "bool"
    return encode_bool arg
  when "int"
    return encode_int arg, type
  when "ureal", "ufixed"
    return encode_ufixed arg, type
  when "real", "fixed"
    return encode_fixed arg, type
  when "string", "bytes"
    return encode_bytes arg, type
  when "hash"
    return encode_hash arg, type
  when "address"
    return encode_address arg
  else
    raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}"
  end
end

def encode_type(type, arg)

Raises:
  • (EncodingError) - if value does not match type.

Returns:
  • (String) - the encoded type.

Parameters:
  • arg (String, Number) -- value to be encoded.
  • type (Eth::Abi::Type) -- type to be encoded.
def encode_type(type, arg)
  if %w(string bytes).include? type.base_type and type.sub_type.empty?
    raise EncodingError, "Argument must be a String" unless arg.instance_of? String
    # encodes strings and bytes
    size = encode_type Type.size_type, arg.size
    padding = BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
    return "#{size}#{arg}#{padding}"
  elsif type.is_dynamic?
    raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array
    # encodes dynamic-sized arrays
    head, tail = "", ""
    head += encode_type Type.size_type, arg.size
    nested_sub = type.nested_sub
    nested_sub_size = type.nested_sub.size
    arg.size.times do |i|
      # ref https://github.com/ethereum/tests/issues/691
      raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
      head += encode_type nested_sub, arg[i]
    end
    return "#{head}#{tail}"
  else
    if type.dimensions.empty?
      # encode a primitive type
      return encode_primitive_type type, arg
    else
      # encode static-size arrays
      return arg.map { |x| encode_type(type.nested_sub, x) }.join
    end
  end
end

def encode_ufixed(arg, type)

def encode_ufixed(arg, type)
  high, low = type.sub_type.split("x").map(&:to_i)
  raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
  return Util.zpad_int((arg * 2 ** low).to_i)
end

def encode_uint(arg, type)

def encode_uint(arg, type)
  raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > UINT_MAX or arg < UINT_MIN
  real_size = type.sub_type.to_i
  i = arg.to_i
  raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size
  return Util.zpad_int i
end