module Ethereum::ABI
Contract ABI
encoding and decoding.
Constants
- VERSION
Public Instance Methods
decode_abi(types, data)
click to toggle source
Decodes multiple arguments using the head/tail mechanism.
# File lib/ethereum/abi.rb, line 170 def decode_abi(types, data) parsed_types = types.map {|t| Type.parse(t) } outputs = [nil] * types.size start_positions = [nil] * types.size + [data.size] # TODO: refactor, a reverse iteration will be better pos = 0 parsed_types.each_with_index do |t, i| # If a type is static, grab the data directly, otherwise record its # start position if t.dynamic? start_positions[i] = Utils.big_endian_to_int(data[pos, 32]) j = i - 1 while j >= 0 && start_positions[j].nil? start_positions[j] = start_positions[i] j -= 1 end pos += 32 else outputs[i] = data[pos, t.size] 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 raise DecodingError, "Not enough data for head" unless pos <= data.size parsed_types.each_with_index do |t, i| if t.dynamic? offset, next_offset = start_positions[i, 2] outputs[i] = data[offset...next_offset] end end parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) } end
Also aliased as: decode
decode_primitive_type(type, data)
click to toggle source
# File lib/ethereum/abi.rb, line 252 def decode_primitive_type(type, data) case type.base when 'address' Utils.encode_hex data[12..-1] when 'string', 'bytes' if type.sub.empty? # dynamic size = Utils.big_endian_to_int data[0,32] data[32..-1][0,size] else # fixed data[0, type.sub.to_i] end when 'hash' data[(32 - type.sub.to_i), type.sub.to_i] when 'uint' Utils.big_endian_to_int data when 'int' u = Utils.big_endian_to_int data u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u when 'ureal', 'ufixed' high, low = type.sub.split('x').map(&:to_i) Utils.big_endian_to_int(data) * 1.0 / 2**low when 'real', 'fixed' high, low = type.sub.split('x').map(&:to_i) u = Utils.big_endian_to_int data i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u i * 1.0 / 2**low when 'bool' data[-1] == BYTE_ONE else raise DecodingError, "Unknown primitive type: #{type.base}" end end
decode_type(type, arg)
click to toggle source
# File lib/ethereum/abi.rb, line 218 def decode_type(type, arg) if %w(string bytes).include?(type.base) && type.sub.empty? l = Utils.big_endian_to_int arg[0,32] data = arg[32..-1] raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Utils.ceil32(l) data[0, l] elsif type.dynamic? l = Utils.big_endian_to_int arg[0,32] 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| Utils.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 elsif !type.dims.empty? # static-sized arrays l = type.dims.last[0] subtype = type.subtype (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) } else decode_primitive_type type, arg end end
encode_abi(types, args)
click to toggle source
Encodes multiple arguments using the head/tail mechanism.
# File lib/ethereum/abi.rb, line 27 def encode_abi(types, args) parsed_types = types.map {|t| Type.parse(t) } head_size = (0...args.size) .map {|i| parsed_types[i].size || 32 } .reduce(0, &:+) head, tail = '', '' args.each_with_index do |arg, i| if parsed_types[i].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 "#{head}#{tail}" end
Also aliased as: encode
encode_primitive_type(type, arg)
click to toggle source
# File lib/ethereum/abi.rb, line 95 def encode_primitive_type(type, arg) case type.base when 'uint' real_size = type.sub.to_i i = get_uint arg raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size Utils.zpad_int i when 'bool' raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass) Utils.zpad_int(arg ? 1: 0) when 'int' real_size = type.sub.to_i i = get_int arg raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1) Utils.zpad_int(i % 2**type.sub.to_i) when 'ureal', 'ufixed' high, low = type.sub.split('x').map(&:to_i) raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**high Utils.zpad_int((arg * 2**low).to_i) when 'real', 'fixed' high, low = type.sub.split('x').map(&:to_i) raise ValueOutOfBounds, arg unless arg >= -2**(high - 1) && arg < 2**(high - 1) i = (arg * 2**low).to_i Utils.zpad_int(i % 2**(high+low)) when 'string', 'bytes' raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String) if type.sub.empty? # variable length type size = Utils.zpad_int arg.size padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size) "#{size}#{arg}#{padding}" else # fixed length type raise ValueOutOfBounds, arg unless arg.size <= type.sub.to_i padding = BYTE_ZERO * (32 - arg.size) "#{arg}#{padding}" end when 'hash' size = type.sub.to_i raise EncodingError, "too long: #{arg}" unless size > 0 && size <= 32 if arg.is_a?(Integer) Utils.zpad_int(arg) elsif arg.size == size Utils.zpad arg, 32 elsif arg.size == size * 2 Utils.zpad_hex arg else raise EncodingError, "Could not parse hash: #{arg}" end when 'address' if arg.is_a?(Integer) Utils.zpad_int arg elsif arg.size == 20 Utils.zpad arg, 32 elsif arg.size == 40 Utils.zpad_hex arg elsif arg.size == 42 && arg[0,2] == '0x' Utils.zpad_hex arg[2..-1] else raise EncodingError, "Could not parse address: #{arg}" end else raise EncodingError, "Unhandled type: #{type.base} #{type.sub}" end end
encode_type(type, arg)
click to toggle source
Encodes a single value (static or dynamic).
@param type [Ethereum::ABI::Type] value type @param arg [Object] value
@return [String] encoded bytes
# File lib/ethereum/abi.rb, line 56 def encode_type(type, arg) if %w(string bytes).include?(type.base) && type.sub.empty? raise ArgumentError, "arg must be a string" unless arg.instance_of?(String) size = encode_type Type.size_type, arg.size padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size) "#{size}#{arg}#{padding}" elsif type.dynamic? raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array) head, tail = '', '' if type.dims.last == 0 head += encode_type(Type.size_type, arg.size) else raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last end sub_type = type.subtype sub_size = type.subtype.size arg.size.times do |i| if sub_size.nil? head += encode_type(Type.size_type, 32*arg.size + tail.size) tail += encode_type(sub_type, arg[i]) else head += encode_type(sub_type, arg[i]) end end "#{head}#{tail}" else # static type if type.dims.empty? encode_primitive_type type, arg else arg.map {|x| encode_type(type.subtype, x) }.join end end end
Private Instance Methods
get_int(n)
click to toggle source
# File lib/ethereum/abi.rb, line 309 def get_int(n) case n when Integer raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN n when String if n.size == 40 i = Utils.big_endian_to_int Utils.decode_hex(n) elsif n.size <= 32 i = Utils.big_endian_to_int n else raise EncodingError, "String too long: #{n}" end i > INT_MAX ? (i-TT256) : i when true 1 when false, nil 0 else raise EncodingError, "Cannot decode int: #{n}" end end
get_uint(n)
click to toggle source
# File lib/ethereum/abi.rb, line 287 def get_uint(n) case n when Integer raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN n when String if n.size == 40 Utils.big_endian_to_int Utils.decode_hex(n) elsif n.size <= 32 Utils.big_endian_to_int n else raise EncodingError, "String too long: #{n}" end when true 1 when false, nil 0 else raise EncodingError, "Cannot decode uint: #{n}" end end