module Eth::Abi
def decode(types, data)
-
(Array)
- the decoded ABI data.
Parameters:
-
data
(String
) -- ABI data to be decoded. -
types
(Array
) -- the ABI to be decoded.
def decode(types, data) # accept hex abi but decode it first data = Util.hex_to_bin data if Util.is_hex? data # parse all types parsed_types = types.map { |t| Type.parse(t) } # prepare output data outputs = [nil] * types.size start_positions = [nil] * types.size + [data.size] pos = 0 parsed_types.each_with_index do |t, i| if t.is_dynamic? # record start position for dynamic type start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32]) j = i - 1 while j >= 0 and start_positions[j].nil? start_positions[j] = start_positions[i] j -= 1 end pos += 32 else # get data directly for static types outputs[i] = data[pos, t.size] pos += t.size end end # add start position equal the length of the entire data j = types.size - 1 while j >= 0 and 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 # add dynamic types parsed_types.each_with_index do |t, i| if t.is_dynamic? offset, next_offset = start_positions[i, 2] outputs[i] = data[offset...next_offset] end end # return the decoded ABI types and data return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) } end
def decode_primitive_type(type, data)
-
(DecodingError)
- if decoding fails for type.
Returns:
-
(String)
- the decoded data for the type.
Parameters:
-
data
(String
) -- encoded primitive type data string. -
type
(Eth::Abi::Type
) -- type to be decoded.
def decode_primitive_type(type, data) case type.base_type when "address" # decoded address with 0x-prefix return "0x#{Util.bin_to_hex data[12..-1]}" when "string", "bytes" if type.sub_type.empty? size = Util.deserialize_big_endian_to_int data[0, 32] # decoded dynamic-sized array return data[32..-1][0, size] else # decoded static-sized array return data[0, type.sub_type.to_i] end when "hash" # decoded hash return data[(32 - type.sub_type.to_i), type.sub_type.to_i] when "uint" # decoded unsigned integer return 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 ** type.sub_type.to_i) : u # decoded integer return i when "ureal", "ufixed" high, low = type.sub_type.split("x").map(&:to_i) # decoded unsigned fixed point numeric return 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 return i * 1.0 / 2 ** low when "bool" # decoded boolean return data[-1] == Constant::BYTE_ONE else raise DecodingError, "Unknown primitive type: #{type.base_type}" end end
def decode_type(type, arg)
-
(DecodingError)
- if decoding fails for type.
Returns:
-
(String)
- the decoded data for the type.
Parameters:
-
arg
(String
) -- encoded type data string. -
type
(Eth::Abi::Type
) -- type to be decoded.
def decode_type(type, arg) if %w(string bytes).include?(type.base_type) and type.sub_type.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 return data[0, l] elsif type.is_dynamic? l = Util.deserialize_big_endian_to_int arg[0, 32] nested_sub = type.nested_sub # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? # decoded dynamic-sized arrays return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) } elsif !type.dimensions.empty? l = type.dimensions.last[0] nested_sub = type.nested_sub # decoded static-size arrays return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) } else # decoded primitive types return decode_primitive_type type, arg end end
def encode(types, args)
-
(String)
- the encoded ABI data.
Parameters:
-
args
(Array
) -- values to be ABI-encoded. -
types
(Array
) -- types to be ABI-encoded.
def encode(types, args) # prase all types parsed_types = types.map { |t| Type.parse(t) } # prepare the "head" head_size = (0...args.size) .map { |i| parsed_types[i].size or 32 } .reduce(0, &:+) head, tail = "", "" # encode types and arguments args.each_with_index do |arg, i| if parsed_types[i].is_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 # return the encoded ABI blob return "#{head}#{tail}" end
def encode_address(arg)
def encode_address(arg) if arg.is_a? Integer # address from integer return Util.zpad_int arg elsif arg.size == 20 # address from encoded address return Util.zpad arg, 32 elsif arg.size == 40 # address from hexa-decimal address with 0x prefix return Util.zpad_hex arg elsif arg.size == 42 and arg[0, 2] == "0x" # address from hexa-decimal address return Util.zpad_hex arg[2..-1] else raise EncodingError, "Could not parse address: #{arg}" end end
def encode_bool(arg)
def encode_bool(arg) raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass return Util.zpad_int(arg ? 1 : 0) end
def encode_bytes(arg, type)
def encode_bytes(arg, type) raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String if type.sub_type.empty? size = Util.zpad_int arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) # variable length string/bytes return "#{size}#{arg}#{padding}" else raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i padding = Constant::BYTE_ZERO * (32 - arg.size) # fixed length string/bytes return "#{arg}#{padding}" end end
def encode_fixed(arg, type)
def encode_fixed(arg, type) high, low = type.sub_type.split("x").map(&:to_i) raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1) i = (arg * 2 ** low).to_i return Util.zpad_int(i % 2 ** (high + low)) end
def encode_hash(arg, type)
def encode_hash(arg, type) size = type.sub_type.to_i raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32 if arg.is_a? Integer # hash from integer return Util.zpad_int arg elsif arg.size == size # hash from encoded hash return Util.zpad arg, 32 elsif arg.size == size * 2 # hash from hexa-decimal hash return Util.zpad_hex arg else raise EncodingError, "Could not parse hash: #{arg}" end end
def encode_int(arg, type)
def encode_int(arg, type) raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN real_size = type.sub_type.to_i i = arg.to_i raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1) return Util.zpad_int(i % 2 ** type.sub_type.to_i) end
def encode_primitive_type(type, arg)
-
(EncodingError)
- if encoding fails for type. -
(ValueOutOfBounds)
- if value is out of bounds for type. -
(EncodingError)
- if value does not match type.
Returns:
-
(String)
- the encoded primitive type.
Parameters:
-
arg
(String|Number
) -- value to be encoded. -
type
(Eth::Abi::Type
) -- type to be encoded.
def encode_primitive_type(type, arg) case type.base_type when "uint" return encode_uint arg, type when "bool" return encode_bool arg when "int" return encode_int arg, type when "ureal", "ufixed" return encode_ufixed arg, type when "real", "fixed" return encode_fixed arg, type when "string", "bytes" return encode_bytes arg, type when "hash" return encode_hash arg, type when "address" return encode_address arg else raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}" end end
def encode_type(type, arg)
-
(EncodingError)
- if value does not match type.
Returns:
-
(String)
- the encoded type.
Parameters:
-
arg
(String|Number
) -- value to be encoded. -
type
(Eth::Abi::Type
) -- type to be encoded.
def encode_type(type, arg) if %w(string bytes).include? type.base_type and type.sub_type.empty? raise EncodingError, "Argument must be a String" unless arg.instance_of? String # encodes strings and bytes size = encode_type Type.size_type, arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) return "#{size}#{arg}#{padding}" elsif type.is_dynamic? raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array # encodes dynamic-sized arrays head, tail = "", "" head += encode_type Type.size_type, arg.size nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size arg.size.times do |i| # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? head += encode_type nested_sub, arg[i] end return "#{head}#{tail}" else if type.dimensions.empty? # encode a primitive type return encode_primitive_type type, arg else # encode static-size arrays return arg.map { |x| encode_type(type.nested_sub, x) }.join end end end
def encode_ufixed(arg, type)
def encode_ufixed(arg, type) high, low = type.sub_type.split("x").map(&:to_i) raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high return Util.zpad_int((arg * 2 ** low).to_i) end
def encode_uint(arg, type)
def encode_uint(arg, type) raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN real_size = type.sub_type.to_i i = arg.to_i raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size return Util.zpad_int i end