module Eth::Abi::Encoder
def address(arg)
def address(arg) if arg.is_a? Address # from checksummed address with 0x prefix Util.zpad_hex arg.to_s[2..-1] elsif arg.is_a? Integer # address from integer Util.zpad_int arg elsif arg.size == 20 # address from encoded address Util.zpad arg, 32 elsif arg.size == 40 # address from hexadecimal address Util.zpad_hex arg elsif arg.size == 42 and arg[0, 2] == "0x" # address from hexadecimal address with 0x prefix Util.zpad_hex arg[2..-1] else raise EncodingError, "Could not parse address: #{arg}" end end
def bool(arg)
def bool(arg) raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass Util.zpad_int(arg ? 1 : 0) end
def bytes(arg, type)
def bytes(arg, type) raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String arg = handle_hex_string arg, type 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 "#{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 "#{arg}#{padding}" end end
def fixed(arg, type)
def fixed(arg, type) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric 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 Util.zpad_int(i % 2 ** (high + low)) end
def handle_hex_string(arg, type)
The ABI encoder needs to be able to determine between a hex `"123"`
def handle_hex_string(arg, type) if Util.prefixed? arg or (arg.size === type.sub_type.to_i * 2 and Util.hex? arg) # There is no way telling whether a string is hex or binary with certainty # in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string. # Additionally, if the string size is exactly the double of the expected # binary size, we can assume a hex value. Util.hex_to_bin arg else # Everything else will be assumed binary or raw string. arg.b end end
def hash(arg, type)
def 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 Util.zpad_int arg elsif arg.size == size # hash from encoded hash Util.zpad arg, 32 elsif arg.size == size * 2 # hash from hexadecimal hash Util.zpad_hex arg else raise EncodingError, "Could not parse hash: #{arg}" end end
def int(arg, type)
def int(arg, type) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric 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) Util.zpad_int(i % 2 ** type.sub_type.to_i) end
def 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 primitive_type(type, arg) case type.base_type when "uint" uint arg, type when "bool" bool arg when "int" int arg, type when "ureal", "ufixed" ufixed arg, type when "real", "fixed" fixed arg, type when "string", "bytes" bytes arg, type when "tuple" tuple arg, type when "hash" hash arg, type when "address" address arg else raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}" end end
def struct_offsets(type, arg)
def struct_offsets(type, arg) result = "" offset = arg.size tails_encoding = arg.map { |a| type(type, a) } arg.size.times do |i| if i == 0 offset *= 32 else offset += tails_encoding[i - 1].size end offset_string = type(Type.size_type, offset) result += offset_string end result end
def tuple(arg, type)
def tuple(arg, type) raise EncodingError, "Expecting Hash: #{arg}" unless arg.instance_of? Hash raise EncodingError, "Expecting #{type.components.size} elements: #{arg}" unless arg.size == type.components.size static_size = 0 type.components.each_with_index do |component, i| if type.components[i].dynamic? static_size += 32 else static_size += Util.ceil32(type.components[i].size || 0) end end dynamic_offset = static_size offsets_and_static_values = [] dynamic_values = [] type.components.each_with_index do |component, i| component_type = type.components[i] if component_type.dynamic? offsets_and_static_values << type(Type.size_type, dynamic_offset) dynamic_value = type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name]) dynamic_values << dynamic_value dynamic_offset += dynamic_value.size else offsets_and_static_values << type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name]) end end offsets_and_static_values.join + dynamic_values.join end
def 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 type(type, arg) if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty? raise EncodingError, "Argument must be a String" unless arg.instance_of? String # encodes strings and bytes size = type Type.size_type, arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) "#{size}#{arg}#{padding}" elsif type.base_type == "tuple" && type.dimensions.size == 1 && type.dimensions[0] != 0 result = "" result += struct_offsets(type.nested_sub, arg) result += arg.map { |x| type(type.nested_sub, x) }.join result elsif type.dynamic? && arg.is_a?(Array) # encodes dynamic-sized arrays head, tail = "", "" head += type(Type.size_type, arg.size) nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size # calculate offsets if %w(string bytes).include?(type.base_type) && type.sub_type.empty? offset = 0 arg.size.times do |i| if i == 0 offset = arg.size * 32 else number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor total_bytes_length = number_of_words * 32 offset += total_bytes_length + 32 end head += type(Type.size_type, offset) end elsif nested_sub.base_type == "tuple" && nested_sub.dynamic? head += struct_offsets(nested_sub, arg) end arg.size.times do |i| head += type nested_sub, arg[i] end "#{head}#{tail}" else if type.dimensions.empty? # encode a primitive type primitive_type type, arg else # encode static-size arrays arg.map { |x| type(type.nested_sub, x) }.join end end end
def ufixed(arg, type)
def ufixed(arg, type) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric high, low = type.sub_type.split("x").map(&:to_i) raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high Util.zpad_int((arg * 2 ** low).to_i) end
def uint(arg, type)
def uint(arg, type) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric 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 Util.zpad_int i end