Class: ABI::Encoder

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

Instance Method Summary collapse

Instance Method Details

#ceil32(x) ⇒ Object



268
269
270
# File 'lib/abicoder/encoder.rb', line 268

def ceil32(x)
  x % 32 == 0 ? x : (x + 32 - x%32)
end

#encode(types, args) ⇒ Object

Encodes multiple arguments using the head/tail mechanism.

returns binary string (with BINARY / ASCII_8BIT encoding)

Raises:

  • (ArgumentError)


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
# File 'lib/abicoder/encoder.rb', line 23

def encode( types, args )
  ## enforce args.size and types.size must match - why? why not?
  raise ArgumentError, "Wrong number of args: found #{args.size}, expecting #{types.size}" unless args.size == types.size


  ## 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 ) }

  ## todo/check:  use args.map (instead of types)
  ##                 might allow encoding less args than types?  - why? why not?
  head_size = types
                .map {|type| type.size || 32 }
                .sum

  head, tail = ''.b, ''.b    ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
  types.each_with_index do |type, i|
    if type.dynamic?
      head += encode_uint256( head_size + tail.size )
      tail += encode_type( type, args[i] )
    else
      head += encode_type( type, args[i] )
    end
  end

  head + tail
end

#encode_address(arg) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/abicoder/encoder.rb', line 211

def encode_address( arg )
  if arg.is_a?( Integer )
    lpad_int( arg )
  elsif arg.size == 20
    ## note: make sure encoding is always binary!!!
    arg = arg.b    if arg.encoding != Encoding::BINARY
    lpad( arg )
  elsif arg.size == 40
    lpad_hex( arg )
  elsif arg.size == 42 && arg[0,2] == '0x'   ## todo/fix: allow 0X too - why? why not?
    lpad_hex( arg[2..-1] )
  else
    raise EncodingError, "Could not parse address: #{arg}"
  end
end

#encode_bool(arg) ⇒ Object

Raises:

  • (ArgumentError)


158
159
160
161
162
# File 'lib/abicoder/encoder.rb', line 158

def encode_bool( arg )
  ## raise EncodingError or ArgumentError - why? why not?
  raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
  lpad( arg ? BYTE_ONE : BYTE_ZERO )   ## was  lpad_int( arg ? 1 : 0 )
end

#encode_bytes(arg, length = nil) ⇒ Object

Raises:



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/abicoder/encoder.rb', line 193

def encode_bytes( arg, length=nil )
  ## raise EncodingError or ArgumentError - why? why not?
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
  arg = arg.b    if arg.encoding != Encoding::BINARY

  if length # fixed length type
    raise ValueOutOfBounds, "invalid bytes length #{length}" if arg.size > length
    raise ValueOutOfBounds, "invalid bytes length #{length}" if length < 0 || length > 32
    rpad( arg )
  else  # variable length type  (if length is nil)
    raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
    size =  lpad_int( arg.size )
    value = rpad( arg, ceil32(arg.size) )
    size + value
  end
end

#encode_dynamic_array(type, args) ⇒ Object

Raises:

  • (ArgumentError)


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/abicoder/encoder.rb', line 74

def encode_dynamic_array( type, args )
 raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)

     head, tail = ''.b, ''.b    ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!

     if type.is_a?( Array )  ## dynamic array
       head += encode_uint256( args.size )
     else  ## fixed array
       raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
     end

     subtype = type.subtype
     args.each do |arg|
       if subtype.dynamic?
         head += encode_uint256( 32*args.size + tail.size )
         tail += encode_type( subtype, arg )
       else
         head += encode_type( subtype, arg )
       end
     end
     head + tail
end

#encode_int(arg, bits) ⇒ Object

Raises:

  • (ArgumentError)


173
174
175
176
177
178
# File 'lib/abicoder/encoder.rb', line 173

def encode_int( arg, bits )
  ## raise EncodingError or ArgumentError - why? why not?
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
  raise ValueOutOfBounds, arg   unless arg >= -2**(bits-1) && arg < 2**(bits-1)
  lpad_int( arg % 2**bits )
end

#encode_primitive_type(type, arg) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/abicoder/encoder.rb', line 133

def encode_primitive_type( type, arg )
  case type
  when Uint
    ## note: for now size in  bits always required
    encode_uint( arg, type.bits )
  when Int
    ## note: for now size in  bits always required
    encode_int( arg, type.bits )
  when Bool
    encode_bool( arg )
  when String
    encode_string( arg )
  when FixedBytes
    encode_bytes( arg, type.length )
  when Bytes
    encode_bytes( arg )
  when Address
    encode_address( arg )
  else
    raise EncodingError, "Unhandled type: #{type.class.name} #{type.format}"
  end
end

#encode_static_array(type, args) ⇒ Object

Raises:

  • (ArgumentError)


98
99
100
101
102
103
# File 'lib/abicoder/encoder.rb', line 98

def encode_static_array( type, args )
   raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
   raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim

   args.map {|arg| encode_type( type.subtype, arg ) }.join
end

#encode_string(arg) ⇒ Object

Raises:



181
182
183
184
185
186
187
188
189
190
# File 'lib/abicoder/encoder.rb', line 181

def encode_string( arg )
  ## raise EncodingError or ArgumentError - why? why not?
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
  arg = arg.b   if arg.encoding != Encoding::BINARY    ## was: name == 'UTF-8'

  raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
  size  =  lpad_int( arg.size )
  value =  rpad( arg, ceil32(arg.size) )
  size + value
end

#encode_tuple(tuple, args) ⇒ Object

todo/check: if static tuple gets encoded different

without offset  (head/tail)

Raises:

  • (ArgumentError)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/abicoder/encoder.rb', line 110

def encode_tuple( tuple, args )
 raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
 raise ArgumentError, "Wrong array size (for tuple): found #{args.size}, expecting #{tuple.type.size} tuple elements" unless args.size == tuple.types.size

    head_size =  tuple.types
                  .map {|type| type.size || 32 }
                  .sum

     head, tail = ''.b, ''.b    ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
     tuple.types.each_with_index do |type, i|
       if type.dynamic?
         head += encode_uint256( head_size + tail.size )
         tail += encode_type( type, args[i] )
       else
         head += encode_type( type, args[i] )
       end
     end

    head + tail
end

#encode_type(type, arg) ⇒ String

Encodes a single value (static or dynamic).

Parameters:

  • type (ABI::Type)

    value type

  • arg (Object)

    value

Returns:



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/abicoder/encoder.rb', line 59

def encode_type( type, arg )
  if type.is_a?( Tuple )
     encode_tuple( type, arg )
  elsif type.is_a?( Array ) || type.is_a?( FixedArray )
     if type.dynamic?
       encode_dynamic_array( type, arg )
     else
       encode_static_array( type, arg )
     end
  else  # assume primitive type
     encode_primitive_type( type, arg )
  end
end

#encode_uint(arg, bits) ⇒ Object

Raises:

  • (ArgumentError)


166
167
168
169
170
171
# File 'lib/abicoder/encoder.rb', line 166

def encode_uint( arg, bits )
  ## raise EncodingError or ArgumentError - why? why not?
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
  raise ValueOutOfBounds, arg   unless arg >= 0 && arg < 2**bits
  lpad_int( arg )
end

#encode_uint256(arg) ⇒ Object



165
# File 'lib/abicoder/encoder.rb', line 165

def encode_uint256( arg ) encode_uint( arg, 256 ); end

#lpad(bin) ⇒ Object

rename to lpad32 or such - why? why not?



241
242
243
244
245
# File 'lib/abicoder/encoder.rb', line 241

def lpad( bin )    ## note: same as builtin String#rjust !!!
  l=32   # note: default l word is 32 bytes
  return bin  if bin.size >= l
  BYTE_ZERO * (l - bin.size) + bin
end

#lpad_hex(hex) ⇒ Object

rename to lpad32_hex or such - why? why not?

Raises:

  • (TypeError)


258
259
260
261
262
263
264
# File 'lib/abicoder/encoder.rb', line 258

def lpad_hex( hex )
  raise TypeError, "Value must be a string"  unless hex.is_a?( ::String )
  raise TypeError, 'Non-hexadecimal digit found' unless hex =~ /\A[0-9a-fA-F]*\z/
  bin = [hex].pack("H*")

  lpad( bin )
end

#lpad_int(n) ⇒ Object

rename to lpad32_int or such - why? why not?

Raises:

  • (ArgumentError)


248
249
250
251
252
253
254
255
# File 'lib/abicoder/encoder.rb', line 248

def lpad_int( n )
  raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
  hex = n.to_s(16)
  hex = "0#{hex}"   if hex.size.odd?
  bin = [hex].pack("H*")

  lpad( bin )
end

#rpad(bin, l = 32) ⇒ Object

encoding helpers / utils

with "hard-coded" fill symbol as BYTE_ZERO


233
234
235
236
237
# File 'lib/abicoder/encoder.rb', line 233

def rpad( bin, l=32 )    ## note: same as builtin String#ljust !!!
  # note: default l word is 32 bytes
  return bin  if bin.size >= l
  bin + BYTE_ZERO * (l - bin.size)
end