Module: Eth::Abi::Decoder

Extended by:
Decoder
Included in:
Decoder
Defined in:
lib/eth/abi/decoder.rb

Overview

Provides a utility module to assist decoding ABIs.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.primitive_type(type, data) ⇒ String

Decodes primitive types.

Parameters:

  • type (Eth::Abi::Type)

    type to be decoded.

  • data (String)

    encoded primitive type data string.

Returns:

  • (String)

    the decoded data for the type.

Raises:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/eth/abi/decoder.rb', line 111

def primitive_type(type, data)
  case type.base_type
  when "address"

    # decoded address with 0x-prefix
    Address.new(Util.bin_to_hex data[12..-1]).to_s.downcase
  when "string", "bytes"
    if type.sub_type.empty?
      size = Util.deserialize_big_endian_to_int data[0, 32]

      # decoded dynamic-sized array
      data[32..-1][0, size]
    else

      # decoded static-sized array
      data[0, type.sub_type.to_i]
    end
  when "hash"

    # decoded hash
    data[(32 - type.sub_type.to_i), type.sub_type.to_i]
  when "uint"

    # decoded unsigned integer
    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 ** 256) : u

    # decoded integer
    i
  when "ureal", "ufixed"
    high, low = type.sub_type.split("x").map(&:to_i)

    # decoded unsigned fixed point numeric
    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
    i * 1.0 / 2 ** low
  when "bool"

    # decoded boolean
    data[-1] == Constant::BYTE_ONE
  else
    raise DecodingError, "Unknown primitive type: #{type.base_type}"
  end
end

.type(type, arg) ⇒ String

Decodes a specific value, either static or dynamic.

Parameters:

  • type (Eth::Abi::Type)

    type to be decoded.

  • arg (String)

    encoded type data string.

Returns:

  • (String)

    the decoded data for the type.

Raises:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/eth/abi/decoder.rb', line 33

def type(type, arg)
  if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
    # Case: decoding a string/bytes
    if type.dimensions.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
      data[0, l]
      # Case: decoding array of string/bytes
    else
      l = Util.deserialize_big_endian_to_int arg[0, 32]
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l

      # Decode each element of the array
      (1..l).map do |i|
        pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
        raise DecodingError, "Offset out of bounds" if pointer < 32 * l || pointer > arg.size - 64
        data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
        raise DecodingError, "Offset out of bounds" if pointer + 32 + Util.ceil32(data_l) > arg.size
        type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
      end
    end
  elsif type.base_type == "tuple"
    offset = 0
    data = {}
    raise DecodingError, "Cannot decode tuples without known components" if type.components.nil?
    type.components.each do |c|
      if c.dynamic?
        pointer = Util.deserialize_big_endian_to_int arg[offset, 32] # Pointer to the size of the array's element
        data_len = Util.deserialize_big_endian_to_int arg[pointer, 32] # length of the element

        data[c.name] = type(c, arg[pointer, Util.ceil32(data_len) + 32])
        offset += 32
      else
        size = c.size
        data[c.name] = type(c, arg[offset, size])
        offset += size
      end
    end
    data
  elsif type.dynamic?
    l = Util.deserialize_big_endian_to_int arg[0, 32]
    nested_sub = type.nested_sub

    if nested_sub.dynamic?
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l
      offsets = (0...l).map do |i|
        off = Util.deserialize_big_endian_to_int arg[32 + 32 * i, 32]
        raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.size - 64
        off
      end
      offsets.map { |off| type(nested_sub, arg[32 + off..]) }
    else
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + nested_sub.size * l
      # decoded dynamic-sized arrays with static sub-types
      (0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
    end
  elsif !type.dimensions.empty?
    l = type.dimensions.first
    nested_sub = type.nested_sub

    # decoded static-size arrays
    (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
  else

    # decoded primitive types
    primitive_type type, arg
  end
end

Instance Method Details

#primitive_type(type, data) ⇒ String

Decodes primitive types.

Parameters:

  • type (Eth::Abi::Type)

    type to be decoded.

  • data (String)

    encoded primitive type data string.

Returns:

  • (String)

    the decoded data for the type.

Raises:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/eth/abi/decoder.rb', line 111

def primitive_type(type, data)
  case type.base_type
  when "address"

    # decoded address with 0x-prefix
    Address.new(Util.bin_to_hex data[12..-1]).to_s.downcase
  when "string", "bytes"
    if type.sub_type.empty?
      size = Util.deserialize_big_endian_to_int data[0, 32]

      # decoded dynamic-sized array
      data[32..-1][0, size]
    else

      # decoded static-sized array
      data[0, type.sub_type.to_i]
    end
  when "hash"

    # decoded hash
    data[(32 - type.sub_type.to_i), type.sub_type.to_i]
  when "uint"

    # decoded unsigned integer
    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 ** 256) : u

    # decoded integer
    i
  when "ureal", "ufixed"
    high, low = type.sub_type.split("x").map(&:to_i)

    # decoded unsigned fixed point numeric
    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
    i * 1.0 / 2 ** low
  when "bool"

    # decoded boolean
    data[-1] == Constant::BYTE_ONE
  else
    raise DecodingError, "Unknown primitive type: #{type.base_type}"
  end
end

#type(type, arg) ⇒ String

Decodes a specific value, either static or dynamic.

Parameters:

  • type (Eth::Abi::Type)

    type to be decoded.

  • arg (String)

    encoded type data string.

Returns:

  • (String)

    the decoded data for the type.

Raises:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/eth/abi/decoder.rb', line 33

def type(type, arg)
  if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
    # Case: decoding a string/bytes
    if type.dimensions.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
      data[0, l]
      # Case: decoding array of string/bytes
    else
      l = Util.deserialize_big_endian_to_int arg[0, 32]
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l

      # Decode each element of the array
      (1..l).map do |i|
        pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
        raise DecodingError, "Offset out of bounds" if pointer < 32 * l || pointer > arg.size - 64
        data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
        raise DecodingError, "Offset out of bounds" if pointer + 32 + Util.ceil32(data_l) > arg.size
        type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
      end
    end
  elsif type.base_type == "tuple"
    offset = 0
    data = {}
    raise DecodingError, "Cannot decode tuples without known components" if type.components.nil?
    type.components.each do |c|
      if c.dynamic?
        pointer = Util.deserialize_big_endian_to_int arg[offset, 32] # Pointer to the size of the array's element
        data_len = Util.deserialize_big_endian_to_int arg[pointer, 32] # length of the element

        data[c.name] = type(c, arg[pointer, Util.ceil32(data_len) + 32])
        offset += 32
      else
        size = c.size
        data[c.name] = type(c, arg[offset, size])
        offset += size
      end
    end
    data
  elsif type.dynamic?
    l = Util.deserialize_big_endian_to_int arg[0, 32]
    nested_sub = type.nested_sub

    if nested_sub.dynamic?
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l
      offsets = (0...l).map do |i|
        off = Util.deserialize_big_endian_to_int arg[32 + 32 * i, 32]
        raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.size - 64
        off
      end
      offsets.map { |off| type(nested_sub, arg[32 + off..]) }
    else
      raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + nested_sub.size * l
      # decoded dynamic-sized arrays with static sub-types
      (0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
    end
  elsif !type.dimensions.empty?
    l = type.dimensions.first
    nested_sub = type.nested_sub

    # decoded static-size arrays
    (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
  else

    # decoded primitive types
    primitive_type type, arg
  end
end