Module: AbiCoderRb

Included in:
EventDecoder
Defined in:
lib/abi_coder_rb.rb,
lib/abi_coder_rb/types.rb,
lib/abi_coder_rb/utils.rb,
lib/abi_coder_rb/decode.rb,
lib/abi_coder_rb/encode.rb,
lib/abi_coder_rb/parser.rb,
lib/abi_coder_rb/version.rb,
lib/abi_coder_rb/decode/decode_array.rb,
lib/abi_coder_rb/decode/decode_tuple.rb,
lib/abi_coder_rb/encode/encode_array.rb,
lib/abi_coder_rb/encode/encode_tuple.rb,
lib/abi_coder_rb/decode/decode_fixed_array.rb,
lib/abi_coder_rb/encode/encode_fixed_array.rb,
lib/abi_coder_rb/decode/decode_primitive_type.rb,
lib/abi_coder_rb/encode/encode_primitive_type.rb

Defined Under Namespace

Classes: Address, Array, Bool, Bytes, DecodingError, EncodingError, FixedArray, FixedBytes, Int, String, Tuple, Type, Uint, ValueError, ValueOutOfBounds

Constant Summary collapse

BYTE_EMPTY =
"".b.freeze
BYTE_ZERO =
"\x00".b.freeze
BYTE_ONE =

note: used for encoding bool for now

"\x01".b.freeze
UINT_MAX =

same as 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

2**256 - 1
UINT_MIN =
0
INT_MAX =

same as 57896044618658097711785492504343953926634992332820282019728792003956564819967

2**255 - 1
INT_MIN =

same as -57896044618658097711785492504343953926634992332820282019728792003956564819968

-2**255      ## same as -57896044618658097711785492504343953926634992332820282019728792003956564819968
VERSION =
"0.2.8"

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#after_decoding_actionObject

Returns the value of attribute after_decoding_action.



29
30
31
# File 'lib/abi_coder_rb.rb', line 29

def after_decoding_action
  @after_decoding_action
end

#before_encoding_actionObject

Returns the value of attribute before_encoding_action.



29
30
31
# File 'lib/abi_coder_rb.rb', line 29

def before_encoding_action
  @before_encoding_action
end

Instance Method Details

#abi_to_int_signed(hex_str, bits) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/abi_coder_rb/utils.rb', line 92

def abi_to_int_signed(hex_str, bits)
  hex_str = "0x#{hex_str}" if hex_str[0, 2] != "0x" || hex_str[0, 2] != "0X"

  # 计算预期的十六进制字符串长度
  expected_length = bits / 4
  extended_hex_str = if hex_str.length < expected_length
                       # 如果输入长度小于预期,根据首位字符扩展字符串
                       extend_char = hex_str[0] == "f" ? "f" : "0"
                       extend_char * (expected_length - hex_str.length) + hex_str
                     else
                       hex_str
                     end

  # 将十六进制字符串转换为二进制字符串
  binary_str = extended_hex_str.to_i(16).to_s(2).rjust(bits, extended_hex_str[0])

  # 检查符号位并转换为整数
  if binary_str[0] == "1" # 负数
    # 取反加一以计算补码,然后转换为负数
    -((binary_str.tr("01", "10").to_i(2) + 1) & ((1 << bits) - 1))
  else # 正数
    binary_str.to_i(2)
  end
end

#after_decoding(action) ⇒ Object



35
36
37
# File 'lib/abi_coder_rb.rb', line 35

def after_decoding(action)
  self.after_decoding_action = action
end

#before_encoding(action) ⇒ Object



31
32
33
# File 'lib/abi_coder_rb.rb', line 31

def before_encoding(action)
  self.before_encoding_action = action
end

#bin_to_hex(bin) ⇒ Object



8
9
10
# File 'lib/abi_coder_rb/utils.rb', line 8

def bin_to_hex(bin)
  bin.each_byte.map { |byte| "%02x" % byte }.join
end

#ceil32(x) ⇒ Object



60
61
62
# File 'lib/abi_coder_rb/utils.rb', line 60

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

#decode(type_str, data) ⇒ Object

Raises:



7
8
9
10
11
# File 'lib/abi_coder_rb/decode.rb', line 7

def decode(type_str, data)
  raise DecodingError, "Empty data" if data.nil? || data.empty?

  decode_type(Type.parse(type_str), data)
end

#decode_array(type, data) ⇒ Object

Raises:



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/abi_coder_rb/decode/decode_array.rb', line 2

def decode_array(type, data)
  size = decode_uint256(data[0, 32])
  raise DecodingError, "Too many elements: #{size}" if size > 100_000

  subtype = type.subtype

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

    start_positions = (1..size).map { |i| 32 + decode_uint256(data[32 * i, 32]) }
    start_positions.push(data.size)

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

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

#decode_fixed_array(type, data) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
# File 'lib/abi_coder_rb/decode/decode_fixed_array.rb', line 2

def decode_fixed_array(type, data)
  l = type.dim
  subtype = type.subtype
  if subtype.dynamic?
    start_positions = (0...l).map { |i| decode_uint256(data[32 * i, 32]) }
    start_positions.push(data.size)

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

    outputs.map { |out| decode_type(subtype, out) }
  else
    (0...l).map { |i| decode_type(subtype, data[subtype.size * i, subtype.size]) }
  end
end

#decode_primitive_type(type, data) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/abi_coder_rb/decode/decode_primitive_type.rb', line 2

def decode_primitive_type(type, data)
  result =
    case type
    when Uint
      decode_uint256(data[0, 32])
    when Int
      abi_to_int_signed(bin_to_hex(data[0, 32]), type.bits)
    when Bool
      data[31] == BYTE_ONE
    when String
      size = decode_uint256(data[0, 32])
      data[32...(32 + size)].force_encoding("UTF-8")
    when Bytes
      size = decode_uint256(data[0, 32])
      data[32...(32 + size)]
    when FixedBytes
      data[0, type.length]
    when Address
      bin_to_hex(data[12...32]).force_encoding("UTF-8")
    else
      raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
    end

  result = after_decoding_action.call(type.format, result) if after_decoding_action

  result
end

#decode_tuple(type, data) ⇒ Object



2
3
4
# File 'lib/abi_coder_rb/decode/decode_tuple.rb', line 2

def decode_tuple(type, data)
  decode_types(type.types, data)
end

#encode(typestr_or_typestrs, value_or_values, packed = false) ⇒ Object

returns byte array



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/abi_coder_rb/encode.rb', line 8

def encode(typestr_or_typestrs, value_or_values, packed = false)
  if typestr_or_typestrs.is_a?(::Array)
    raise EncodingError, "values should be an array" unless value_or_values.is_a?(::Array)

    typestrs = typestr_or_typestrs
    values = value_or_values
    typestrs.map.with_index do |typestr, i|
      value = values[i]
      encode(typestr, value, packed)
    end.join
  else
    typestr = typestr_or_typestrs
    value = value_or_values
    # TODO: more checks?
    raise EncodingError, "Value can not be nil" if value.nil?

    parsed = Type.parse(typestr)
    encode_type(parsed, value, packed)
  end
end

#encode_address(arg, packed = false) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 105

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

#encode_array(type, args, packed = false) ⇒ Object

Raises:

  • (ArgumentError)


2
3
4
5
6
# File 'lib/abi_coder_rb/encode/encode_array.rb', line 2

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

  _encode_array(type: type, args: args, packed: packed)
end

#encode_bool(arg, packed = false) ⇒ Object

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 53

def encode_bool(arg, packed = false)
  ## raise EncodingError or ArgumentError - why? why not?
  raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)

  if packed
    arg ? BYTE_ONE : BYTE_ZERO
  else
    lpad(arg ? BYTE_ONE : BYTE_ZERO) ## was  lpad_int( arg ? 1 : 0 )
  end
end

#encode_bytes(arg, length: nil, packed: false) ⇒ Object

Raises:



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 81

def encode_bytes(arg, length: nil, packed: false)
  ## 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 #{arg.size}, should be #{length}" if arg.size > length
    raise ValueOutOfBounds, "invalid bytes length #{length}" if length < 0 || length > 32

    packed ? arg : 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

    if packed
      arg
    else
      size =  lpad_int(arg.size)
      value = rpad(arg, ceil32(arg.size))
      size + value
    end
  end
end

#encode_fixed_array(type, args, packed = false) ⇒ Object

Raises:

  • (ArgumentError)


2
3
4
5
6
7
8
9
10
# File 'lib/abi_coder_rb/encode/encode_fixed_array.rb', line 2

def encode_fixed_array(type, args, packed = false)
  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

  # fixed_array,是没有元素数量的编码de
  # 如果内部类型是静态的,就是一个一个元素编码后加起来。
  # 如果内部类型是动态的,先用位置一个一个编码加起来,然后是元素本体
  _encode_array(type: type, args: args, packed: packed)
end

#encode_int(arg, bits, packed = false) ⇒ Object

Raises:

  • (ArgumentError)


42
43
44
45
46
47
48
49
50
51
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 42

def encode_int(arg, bits, packed = false)
  ## raise EncodingError or ArgumentError - why? why not?
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)

  if packed
    hex_to_bin(int_to_abi_signed(arg, bits))
  else
    hex_to_bin(int_to_abi_signed_256bit(arg))
  end
end

#encode_primitive_type(type, arg, packed = false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 2

def encode_primitive_type(type, arg, packed = false)
  arg = before_encoding_action.call(type.format, arg) if before_encoding_action
  # 根据类型选择相应的编码方法
  case type
  when Uint
    # NOTE: for now size in  bits always required
    encode_uint(arg, type.bits, packed)
  when Int
    # NOTE: for now size in  bits always required
    encode_int(arg, type.bits, packed)
  when Bool
    encode_bool(arg, packed)
  when String
    encode_string(arg, packed)
  when FixedBytes
    encode_bytes(arg, length: type.length, packed: packed)
  when Bytes
    encode_bytes(arg, packed: packed)
  when Address
    encode_address(arg, packed)
  else
    raise EncodingError, "Unknown type: #{type}"
  end
end

#encode_string(arg, packed = false) ⇒ Object

Raises:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 64

def encode_string(arg, packed = false)
  ## raise EncodingError or ArgumentError - why? why not?
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)

  arg = arg.b if arg.encoding != "BINARY" ## was: name == 'UTF-8', wasm

  raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX

  if packed
    arg
  else
    size = lpad_int(arg.size)
    value = rpad(arg, ceil32(arg.size))
    size + value
  end
end

#encode_tuple(tuple, args, packed = false) ⇒ Object



2
3
4
5
6
# File 'lib/abi_coder_rb/encode/encode_tuple.rb', line 2

def encode_tuple(tuple, args, packed = false)
  raise "#{tuple.class} with multi inner types is not supported in packed mode" if packed && tuple.types.size > 1

  encode_types(tuple.types, args, packed)
end

#encode_uint(arg, bits, packed = false) ⇒ Object

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
35
36
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 27

def encode_uint(arg, bits, packed = false)
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
  raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**bits

  if packed
    lpad_int(arg, bits / 8)
  else
    lpad_int(arg)
  end
end

#encode_uint256(arg) ⇒ Object



38
39
40
# File 'lib/abi_coder_rb/encode/encode_primitive_type.rb', line 38

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

#hex?(str) ⇒ Boolean

Returns:

  • (Boolean)


12
13
14
# File 'lib/abi_coder_rb/utils.rb', line 12

def hex?(str)
  str.start_with?("0x") && str.length.even? && str[2..].match?(/\A\b[0-9a-fA-F]+\b\z/)
end

#hex_to_bin(hex) ⇒ Object Also known as: hex



2
3
4
5
# File 'lib/abi_coder_rb/utils.rb', line 2

def hex_to_bin(hex)
  hex = hex[2..] if %w[0x 0X].include?(hex[0, 2]) ## cut-of leading 0x or 0X if present
  hex.scan(/../).map { |x| x.hex.chr }.join
end

#int_to_abi_signed(value, bits) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/abi_coder_rb/utils.rb', line 64

def int_to_abi_signed(value, bits)
  min = -2**(bits - 1)
  max = 2**(bits - 1) - 1
  raise "Value out of range" if value < min || value > max

  value = (1 << bits) + value if value < 0

  hex_str = value.to_s(16)

  hex_str.rjust(bits / 4, "0")
end

#int_to_abi_signed_256bit(value) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/abi_coder_rb/utils.rb', line 76

def int_to_abi_signed_256bit(value)
  # 确保值在256位有符号整数范围内
  min = -2**255
  max = 2**255 - 1
  raise "Value out of range" if value < min || value > max

  # 为负数计算补码
  value = (1 << 256) + value if value < 0

  # 转换为十六进制字符串
  hex_str = value.to_s(16)

  # 确保字符串长度为64字符(256位)
  hex_str.rjust(64, "0")
end

#lpad(bin, l = 32) ⇒ Object

rename to lpad32 or such - why? why not? example: lpad(“hello”, ‘x’, 10) => “xxxxxxhello”



30
31
32
33
34
# File 'lib/abi_coder_rb/utils.rb', line 30

def lpad(bin, l = 32) ## note: same as builtin String#rjust !!!
  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)


51
52
53
54
55
56
57
58
# File 'lib/abi_coder_rb/utils.rb', line 51

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_to_bin(hex)

  lpad(bin)
end

#lpad_int(n, l = 32) ⇒ Object

rename to lpad32_int or such - why? why not?



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/abi_coder_rb/utils.rb', line 37

def lpad_int(n, l = 32)
  unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
    raise ArgumentError,
          "Integer invalid or out of range: #{n}"
  end

  hex = n.to_s(16)
  hex = "0#{hex}" if hex.length.odd? # wasm, no .odd?
  bin = hex_to_bin(hex)

  lpad(bin, l)
end

#rpad(bin, l = 32) ⇒ Object

encoding helpers / utils

with "hard-coded" fill symbol as BYTE_ZERO


20
21
22
23
24
25
# File 'lib/abi_coder_rb/utils.rb', line 20

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