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:



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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/eth/abi/decoder.rb', line 129

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
      decoded = data[32..-1][0, size]
      decoded.force_encoding(Encoding::UTF_8)
      decoded
    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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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" && type.dimensions.empty?
    offset = 0
    result = []
    raise DecodingError, "Cannot decode tuples without known components" if type.components.nil?
    type.components.each_with_index do |c, i|
      if c.dynamic?
        pointer = Util.deserialize_big_endian_to_int arg[offset, 32]
        next_offset = if i + 1 < type.components.size
            Util.deserialize_big_endian_to_int arg[offset + 32, 32]
          else
            arg.size
          end
        raise DecodingError, "Offset out of bounds" if pointer > arg.size || next_offset > arg.size || next_offset < pointer
        result << type(c, arg[pointer, next_offset - pointer])
        offset += 32
      else
        size = c.size
        raise DecodingError, "Offset out of bounds" if offset + size > arg.size
        result << type(c, arg[offset, size])
        offset += size
      end
    end
    result
  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

    if nested_sub.dynamic?
      raise DecodingError, "Wrong data size for static array" unless arg.size >= 32 * l
      offsets = (0...l).map do |i|
        off = Util.deserialize_big_endian_to_int arg[32 * i, 32]
        raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.size - 32
        off
      end
      offsets.each_with_index.map do |off, i|
        size = (i + 1 < offsets.length ? offsets[i + 1] : arg.size) - off
        type(nested_sub, arg[off, size])
      end
    else
      # decoded static-size arrays with static sub-types
      (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
    end
  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:



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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/eth/abi/decoder.rb', line 129

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
      decoded = data[32..-1][0, size]
      decoded.force_encoding(Encoding::UTF_8)
      decoded
    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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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" && type.dimensions.empty?
    offset = 0
    result = []
    raise DecodingError, "Cannot decode tuples without known components" if type.components.nil?
    type.components.each_with_index do |c, i|
      if c.dynamic?
        pointer = Util.deserialize_big_endian_to_int arg[offset, 32]
        next_offset = if i + 1 < type.components.size
            Util.deserialize_big_endian_to_int arg[offset + 32, 32]
          else
            arg.size
          end
        raise DecodingError, "Offset out of bounds" if pointer > arg.size || next_offset > arg.size || next_offset < pointer
        result << type(c, arg[pointer, next_offset - pointer])
        offset += 32
      else
        size = c.size
        raise DecodingError, "Offset out of bounds" if offset + size > arg.size
        result << type(c, arg[offset, size])
        offset += size
      end
    end
    result
  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

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

    # decoded primitive types
    primitive_type type, arg
  end
end