eth 0.5.9 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
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