Class: ABI::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/abicoder/decoder.rb

Instance Method Summary collapse

Instance Method Details

#big_endian_to_int(bin) ⇒ Object

decoding helpers / utils



192
193
194
195
196
197
198
# File 'lib/abicoder/decoder.rb', line 192

def big_endian_to_int( bin )
  bin = bin.sub( /\A(\x00)+/, '' )   ## keep "performance" shortcut - why? why not?
  ### todo/check - allow nil - why? why not?
  ##  raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
  bin = bin || BYTE_ZERO
  bin.unpack("H*").first.to_i(16)
end

#decode(types, data, raise_errors = false) ⇒ Object

Decodes multiple arguments using the head/tail mechanism.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
# File 'lib/abicoder/decoder.rb', line 9

def decode( types, data, raise_errors = false )
  ## for convenience check if types is a String
  ##   otherwise assume ABI::Type already
  types = types.map { |type| type.is_a?( Type ) ? type : Type.parse( type ) }

  outputs = [nil] * types.size
  start_positions = [nil] * types.size + [data.size]

  # TODO: refactor, a reverse iteration will be better
  pos = 0
  types.each_with_index do |t, i|
    # If a type is static, grab the data directly, otherwise record its
    # start position
    if t.dynamic?

      if pos>data.size-1
        if raise_errors
          raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
        else
          puts "!! WARN - DecodingError: Position out of bounds #{pos}>#{data.size-1}"
        end
      end

      start_positions[i] = big_endian_to_int(data[pos, 32])

      if start_positions[i]>data.size-1
        if raise_errors
          raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
        else
          puts "!! WARN - DecodingError: Start position out of bounds #{start_positions[i]}>#{data.size-1}"
        end
      end


      j = i - 1
      while j >= 0 && start_positions[j].nil?
        start_positions[j] = start_positions[i]
        j -= 1
      end

      pos += 32
    else
      ## puts "step 1 - decode item [#{i}] - #{t.format}  size: #{t.size} dynamic? #{t.dynamic?}"

      outputs[i] = zero_padding( data, pos, t.size, start_positions )
      pos += t.size
    end
  end



  # We add a start position equal to the length of the entire data for
  # convenience.
  j = types.size - 1
  while j >= 0 && start_positions[j].nil?
    start_positions[j] = start_positions[types.size]
    j -= 1
  end

  if pos > data.size
    if raise_errors
      raise DecodingError, "Not enough data for head"
    else
      puts "!! WARN - DecodingError: Not enough data for head"
    end
  end


  types.each_with_index do |t, i|
    if t.dynamic?
      offset, next_offset = start_positions[i, 2]
      if offset<=data.size && next_offset<=data.size
        outputs[i] = data[offset...next_offset]
      end
    end
  end

  if outputs.include?(nil)
    if raise_errors
      raise DecodingError, "Not all data can be parsed"
    else
      puts "!! WARN: DecodingError - Not all data can be parsed"
    end
  end


  types.zip(outputs).map do |(t, out)|
     ## puts "step 2 - decode item - #{t.format} got: #{out.size} byte(s) -  size: #{t.size} dynamic? #{t.dynamic?}"

     decode_type(t, out)
  end
end

#decode_primitive_type(type, data) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/abicoder/decoder.rb', line 150

def decode_primitive_type(type, data)
  case type
  when Address
    encode_hex( data[12..-1] )
  when String, Bytes
      if data.length == 32
        data[0..32]
      else
        size = big_endian_to_int( data[0,32] )
        data[32..-1][0,size]
      end
  when FixedBytes
      data[0, type.length]
  when Uint
    big_endian_to_int( data )
  when Int
    u = big_endian_to_int( data )
    u >= 2**(type.bits-1) ? (u - 2**type.bits) : u
  when Bool
    data[-1] == BYTE_ONE
  else
    raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
  end
end

#decode_type(type, arg) ⇒ Object



104
105
106
107
108
109
110
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
# File 'lib/abicoder/decoder.rb', line 104

def decode_type( type, arg )
  return nil    if arg.nil? || arg.empty?


  if type.is_a?( String ) || type.is_a?( Bytes )
    l = big_endian_to_int( arg[0,32] )
    data = arg[32..-1]
    data[0, l]
  elsif type.is_a?( Tuple )
      arg ? decode(type.types, arg) : []
  elsif type.is_a?( FixedArray )   # static-sized arrays
    l       = type.dim
    subtype = type.subtype
    if subtype.dynamic?
      start_positions = (0...l).map {|i| big_endian_to_int(arg[32*i, 32]) }
      start_positions.push arg.size

      outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }

      outputs.map {|out| decode_type(subtype, out) }
    else
      (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
    end
  elsif type.is_a?( Array )
    l = big_endian_to_int( arg[0,32] )
    raise DecodingError, "Too long length: #{l}"  if l > 100000
    subtype = type.subtype

    if subtype.dynamic?
      raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l

      start_positions = (1..l).map {|i| 32+big_endian_to_int(arg[32*i, 32]) }
      start_positions.push arg.size

      outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }

      outputs.map {|out| decode_type(subtype, out) }
    else
      (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
    end
  else
    decode_primitive_type( type, arg )
  end
end

#encode_hex(bin) ⇒ Object

bin_to_hex

Raises:

  • (TypeError)


200
201
202
203
# File 'lib/abicoder/decoder.rb', line 200

def encode_hex( bin )   ## bin_to_hex
  raise TypeError, "Value must be a String" unless bin.is_a?(::String)
  bin.unpack("H*").first
end

#zero_padding(data, pos, count, start_positions) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/abicoder/decoder.rb', line 176

def zero_padding( data, pos, count, start_positions )
  if pos >= data.size
    start_positions[start_positions.size-1] += count
    "\x00"*count
  elsif pos + count > data.size
    start_positions[start_positions.size-1] += ( count - (data.size-pos))
    data[pos,data.size-pos] + "\x00"*( count - (data.size-pos))
  else
    data[pos, count]
  end
end