eth 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/eth.gemspec CHANGED
@@ -34,6 +34,9 @@ Gem::Specification.new do |spec|
34
34
  spec.platform = Gem::Platform::RUBY
35
35
  spec.required_ruby_version = ">= 2.7", "< 4.0"
36
36
 
37
+ # forwardable for contracts meta programming
38
+ spec.add_dependency "forwardable", "~> 1.3"
39
+
37
40
  # keccak for hashing everything in ethereum
38
41
  spec.add_dependency "keccak", "~> 1.3"
39
42
 
@@ -41,7 +44,7 @@ Gem::Specification.new do |spec|
41
44
  spec.add_dependency "konstructor", "~> 1.0"
42
45
 
43
46
  # rbsecp256k1 for key-pairs and signatures
44
- spec.add_dependency "rbsecp256k1", "~> 5.1"
47
+ spec.add_dependency "rbsecp256k1", "~> 6.0"
45
48
 
46
49
  # openssl for encrypted key derivation
47
50
  spec.add_dependency "openssl", ">= 2.2", "< 4.0"
@@ -0,0 +1,135 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -*- encoding : ascii-8bit -*-
16
+
17
+ # Provides the {Eth} module.
18
+ module Eth
19
+
20
+ # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a utility module to assist decoding ABIs.
24
+ module Decoder
25
+ extend self
26
+
27
+ # Decodes a specific value, either static or dynamic.
28
+ #
29
+ # @param type [Eth::Abi::Type] type to be decoded.
30
+ # @param arg [String] encoded type data string.
31
+ # @return [String] the decoded data for the type.
32
+ # @raise [DecodingError] if decoding fails for type.
33
+ def type(type, arg)
34
+ if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
35
+ # Case: decoding a string/bytes
36
+ if type.dimensions.empty?
37
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
38
+ data = arg[32..-1]
39
+ raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l)
40
+
41
+ # decoded strings and bytes
42
+ data[0, l]
43
+ # Case: decoding array of string/bytes
44
+ else
45
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
46
+
47
+ # Decode each element of the array
48
+ (1..l).map do |i|
49
+ pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
50
+ data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
51
+ type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
52
+ end
53
+ end
54
+ elsif type.dynamic?
55
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
56
+ nested_sub = type.nested_sub
57
+
58
+ # ref https://github.com/ethereum/tests/issues/691
59
+ raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.dynamic?
60
+
61
+ # decoded dynamic-sized arrays
62
+ (0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
63
+ elsif !type.dimensions.empty?
64
+ l = type.dimensions.first
65
+ nested_sub = type.nested_sub
66
+
67
+ # decoded static-size arrays
68
+ (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
69
+ else
70
+
71
+ # decoded primitive types
72
+ primitive_type type, arg
73
+ end
74
+ end
75
+
76
+ # Decodes primitive types.
77
+ #
78
+ # @param type [Eth::Abi::Type] type to be decoded.
79
+ # @param data [String] encoded primitive type data string.
80
+ # @return [String] the decoded data for the type.
81
+ # @raise [DecodingError] if decoding fails for type.
82
+ def primitive_type(type, data)
83
+ case type.base_type
84
+ when "address"
85
+
86
+ # decoded address with 0x-prefix
87
+ "0x#{Util.bin_to_hex data[12..-1]}"
88
+ when "string", "bytes"
89
+ if type.sub_type.empty?
90
+ size = Util.deserialize_big_endian_to_int data[0, 32]
91
+
92
+ # decoded dynamic-sized array
93
+ data[32..-1][0, size]
94
+ else
95
+
96
+ # decoded static-sized array
97
+ data[0, type.sub_type.to_i]
98
+ end
99
+ when "hash"
100
+
101
+ # decoded hash
102
+ data[(32 - type.sub_type.to_i), type.sub_type.to_i]
103
+ when "uint"
104
+
105
+ # decoded unsigned integer
106
+ Util.deserialize_big_endian_to_int data
107
+ when "int"
108
+ u = Util.deserialize_big_endian_to_int data
109
+ i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u
110
+
111
+ # decoded integer
112
+ i
113
+ when "ureal", "ufixed"
114
+ high, low = type.sub_type.split("x").map(&:to_i)
115
+
116
+ # decoded unsigned fixed point numeric
117
+ Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
118
+ when "real", "fixed"
119
+ high, low = type.sub_type.split("x").map(&:to_i)
120
+ u = Util.deserialize_big_endian_to_int data
121
+ i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
122
+
123
+ # decoded fixed point numeric
124
+ i * 1.0 / 2 ** low
125
+ when "bool"
126
+
127
+ # decoded boolean
128
+ data[-1] == Constant::BYTE_ONE
129
+ else
130
+ raise DecodingError, "Unknown primitive type: #{type.base_type}"
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,304 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -*- encoding : ascii-8bit -*-
16
+
17
+ # Provides the {Eth} module.
18
+ module Eth
19
+
20
+ # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a utility module to assist encoding ABIs.
24
+ module Encoder
25
+ extend self
26
+
27
+ # Encodes a specific value, either static or dynamic.
28
+ #
29
+ # @param type [Eth::Abi::Type] type to be encoded.
30
+ # @param arg [String|Number] value to be encoded.
31
+ # @return [String] the encoded type.
32
+ # @raise [EncodingError] if value does not match type.
33
+ def type(type, arg)
34
+ if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty?
35
+ raise EncodingError, "Argument must be a String" unless arg.instance_of? String
36
+
37
+ # encodes strings and bytes
38
+ size = type Type.size_type, arg.size
39
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
40
+ "#{size}#{arg}#{padding}"
41
+ elsif type.base_type == "tuple" && type.dimensions.size == 1 && type.dimensions[0] != 0
42
+ result = ""
43
+ result += struct_offsets(type.nested_sub, arg)
44
+ result += arg.map { |x| type(type.nested_sub, x) }.join
45
+ result
46
+ elsif type.dynamic? && arg.is_a?(Array)
47
+
48
+ # encodes dynamic-sized arrays
49
+ head, tail = "", ""
50
+ head += type(Type.size_type, arg.size)
51
+ nested_sub = type.nested_sub
52
+ nested_sub_size = type.nested_sub.size
53
+
54
+ # calculate offsets
55
+ if %w(string bytes).include?(type.base_type) && type.sub_type.empty?
56
+ offset = 0
57
+ arg.size.times do |i|
58
+ if i == 0
59
+ offset = arg.size * 32
60
+ else
61
+ number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
62
+ total_bytes_length = number_of_words * 32
63
+ offset += total_bytes_length + 32
64
+ end
65
+
66
+ head += type(Type.size_type, offset)
67
+ end
68
+ elsif nested_sub.base_type == "tuple" && nested_sub.dynamic?
69
+ head += struct_offsets(nested_sub, arg)
70
+ end
71
+
72
+ arg.size.times do |i|
73
+ head += type nested_sub, arg[i]
74
+ end
75
+ "#{head}#{tail}"
76
+ else
77
+ if type.dimensions.empty?
78
+
79
+ # encode a primitive type
80
+ primitive_type type, arg
81
+ else
82
+
83
+ # encode static-size arrays
84
+ arg.map { |x| type(type.nested_sub, x) }.join
85
+ end
86
+ end
87
+ end
88
+
89
+ # Encodes primitive types.
90
+ #
91
+ # @param type [Eth::Abi::Type] type to be encoded.
92
+ # @param arg [String|Number] value to be encoded.
93
+ # @return [String] the encoded primitive type.
94
+ # @raise [EncodingError] if value does not match type.
95
+ # @raise [ValueOutOfBounds] if value is out of bounds for type.
96
+ # @raise [EncodingError] if encoding fails for type.
97
+ def primitive_type(type, arg)
98
+ case type.base_type
99
+ when "uint"
100
+ uint arg, type
101
+ when "bool"
102
+ bool arg
103
+ when "int"
104
+ int arg, type
105
+ when "ureal", "ufixed"
106
+ ufixed arg, type
107
+ when "real", "fixed"
108
+ fixed arg, type
109
+ when "string", "bytes"
110
+ bytes arg, type
111
+ when "tuple"
112
+ tuple arg, type
113
+ when "hash"
114
+ hash arg, type
115
+ when "address"
116
+ address arg
117
+ else
118
+ raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}"
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ # Properly encodes unsigned integers.
125
+ def uint(arg, type)
126
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
127
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN
128
+ real_size = type.sub_type.to_i
129
+ i = arg.to_i
130
+ raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size
131
+ Util.zpad_int i
132
+ end
133
+
134
+ # Properly encodes signed integers.
135
+ def int(arg, type)
136
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
137
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN
138
+ real_size = type.sub_type.to_i
139
+ i = arg.to_i
140
+ raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1)
141
+ Util.zpad_int(i % 2 ** type.sub_type.to_i)
142
+ end
143
+
144
+ # Properly encodes booleans.
145
+ def bool(arg)
146
+ raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass
147
+ Util.zpad_int(arg ? 1 : 0)
148
+ end
149
+
150
+ # Properly encodes unsigned fixed-point numbers.
151
+ def ufixed(arg, type)
152
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
153
+ high, low = type.sub_type.split("x").map(&:to_i)
154
+ raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
155
+ Util.zpad_int((arg * 2 ** low).to_i)
156
+ end
157
+
158
+ # Properly encodes signed fixed-point numbers.
159
+ def fixed(arg, type)
160
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
161
+ high, low = type.sub_type.split("x").map(&:to_i)
162
+ raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
163
+ i = (arg * 2 ** low).to_i
164
+ Util.zpad_int(i % 2 ** (high + low))
165
+ end
166
+
167
+ # Properly encodes byte-strings.
168
+ def bytes(arg, type)
169
+ raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String
170
+ arg = handle_hex_string arg, type
171
+
172
+ if type.sub_type.empty?
173
+ size = Util.zpad_int arg.size
174
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
175
+
176
+ # variable length string/bytes
177
+ "#{size}#{arg}#{padding}"
178
+ else
179
+ raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i
180
+ padding = Constant::BYTE_ZERO * (32 - arg.size)
181
+
182
+ # fixed length string/bytes
183
+ "#{arg}#{padding}"
184
+ end
185
+ end
186
+
187
+ # Properly encodes tuples.
188
+ def tuple(arg, type)
189
+ raise EncodingError, "Expecting Hash: #{arg}" unless arg.instance_of? Hash
190
+ raise EncodingError, "Expecting #{type.components.size} elements: #{arg}" unless arg.size == type.components.size
191
+
192
+ static_size = 0
193
+ type.components.each_with_index do |component, i|
194
+ if type.components[i].dynamic?
195
+ static_size += 32
196
+ else
197
+ static_size += Util.ceil32(type.components[i].size || 0)
198
+ end
199
+ end
200
+
201
+ dynamic_offset = static_size
202
+ offsets_and_static_values = []
203
+ dynamic_values = []
204
+
205
+ type.components.each_with_index do |component, i|
206
+ component_type = type.components[i]
207
+ if component_type.dynamic?
208
+ offsets_and_static_values << type(Type.size_type, dynamic_offset)
209
+ dynamic_value = type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name])
210
+ dynamic_values << dynamic_value
211
+ dynamic_offset += dynamic_value.size
212
+ else
213
+ offsets_and_static_values << type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name])
214
+ end
215
+ end
216
+
217
+ offsets_and_static_values.join + dynamic_values.join
218
+ end
219
+
220
+ # Properly encode struct offsets.
221
+ def struct_offsets(type, arg)
222
+ result = ""
223
+ offset = arg.size
224
+ tails_encoding = arg.map { |a| type(type, a) }
225
+ arg.size.times do |i|
226
+ if i == 0
227
+ offset *= 32
228
+ else
229
+ offset += tails_encoding[i - 1].size
230
+ end
231
+ offset_string = type(Type.size_type, offset)
232
+ result += offset_string
233
+ end
234
+ result
235
+ end
236
+
237
+ # Properly encodes hash-strings.
238
+ def hash(arg, type)
239
+ size = type.sub_type.to_i
240
+ raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32
241
+ if arg.is_a? Integer
242
+
243
+ # hash from integer
244
+ Util.zpad_int arg
245
+ elsif arg.size == size
246
+
247
+ # hash from encoded hash
248
+ Util.zpad arg, 32
249
+ elsif arg.size == size * 2
250
+
251
+ # hash from hexadecimal hash
252
+ Util.zpad_hex arg
253
+ else
254
+ raise EncodingError, "Could not parse hash: #{arg}"
255
+ end
256
+ end
257
+
258
+ # Properly encodes addresses.
259
+ def address(arg)
260
+ if arg.is_a? Address
261
+
262
+ # from checksummed address with 0x prefix
263
+ Util.zpad_hex arg.to_s[2..-1]
264
+ elsif arg.is_a? Integer
265
+
266
+ # address from integer
267
+ Util.zpad_int arg
268
+ elsif arg.size == 20
269
+
270
+ # address from encoded address
271
+ Util.zpad arg, 32
272
+ elsif arg.size == 40
273
+
274
+ # address from hexadecimal address
275
+ Util.zpad_hex arg
276
+ elsif arg.size == 42 and arg[0, 2] == "0x"
277
+
278
+ # address from hexadecimal address with 0x prefix
279
+ Util.zpad_hex arg[2..-1]
280
+ else
281
+ raise EncodingError, "Could not parse address: #{arg}"
282
+ end
283
+ end
284
+
285
+ # The ABI encoder needs to be able to determine between a hex `"123"`
286
+ # and a binary `"123"` string.
287
+ def handle_hex_string(arg, type)
288
+ if Util.prefixed? arg or
289
+ (arg.size === type.sub_type.to_i * 2 and Util.hex? arg)
290
+
291
+ # There is no way telling whether a string is hex or binary with certainty
292
+ # in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string.
293
+ # Additionally, if the string size is exactly the double of the expected
294
+ # binary size, we can assume a hex value.
295
+ Util.hex_to_bin arg
296
+ else
297
+
298
+ # Everything else will be assumed binary or raw string.
299
+ arg.b
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
data/lib/eth/abi/event.rb CHANGED
@@ -29,10 +29,21 @@ module Eth
29
29
  # @param interface [Hash] ABI event interface.
30
30
  # @return [String] a hex-string topic.
31
31
  def compute_topic(interface)
32
- sig = Abi.signature(interface)
32
+ sig = signature(interface)
33
33
  Util.prefix_hex(Util.bin_to_hex(Util.keccak256(sig)))
34
34
  end
35
35
 
36
+ # Build event signature string from ABI interface.
37
+ #
38
+ # @param interface [Hash] ABI event interface.
39
+ # @return [String] interface signature string.
40
+ def signature(interface)
41
+ name = interface.fetch("name")
42
+ inputs = interface.fetch("inputs", [])
43
+ types = inputs.map { |i| i.fetch("type") }
44
+ "#{name}(#{types.join(",")})"
45
+ end
46
+
36
47
  # A decoded event log.
37
48
  class LogDescription
38
49
  # The event ABI interface used to decode the log.
@@ -70,7 +81,7 @@ module Eth
70
81
 
71
82
  # The event signature. (e.g. Transfer(address,address,uint256))
72
83
  def signature
73
- @signature ||= Abi.signature(event_interface)
84
+ @signature ||= Abi::Event.signature(event_interface)
74
85
  end
75
86
  end
76
87
 
data/lib/eth/abi/type.rb CHANGED
@@ -86,7 +86,7 @@ module Eth
86
86
  @base_type = base_type
87
87
  @sub_type = sub_type
88
88
  @dimensions = dims.map { |x| x[1...-1].to_i }
89
- @components = components.map { |component| Eth::Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } unless components.nil?
89
+ @components = components.map { |component| Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } unless components.nil?
90
90
  @name = component_name
91
91
  end
92
92