eth 0.4.18 → 0.5.2

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +6 -2
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.github/workflows/spec.yml +52 -0
  5. data/.gitignore +24 -24
  6. data/.gitmodules +3 -3
  7. data/.yardopts +1 -0
  8. data/AUTHORS.txt +27 -0
  9. data/CHANGELOG.md +63 -13
  10. data/Gemfile +12 -4
  11. data/LICENSE.txt +202 -22
  12. data/README.md +231 -76
  13. data/bin/console +4 -4
  14. data/bin/setup +5 -4
  15. data/codecov.yml +6 -0
  16. data/eth.gemspec +23 -19
  17. data/lib/eth/abi/type.rb +178 -0
  18. data/lib/eth/abi.rb +396 -0
  19. data/lib/eth/address.rb +57 -10
  20. data/lib/eth/api.rb +223 -0
  21. data/lib/eth/chain.rb +151 -0
  22. data/lib/eth/client/http.rb +63 -0
  23. data/lib/eth/client/ipc.rb +50 -0
  24. data/lib/eth/client.rb +232 -0
  25. data/lib/eth/constant.rb +71 -0
  26. data/lib/eth/eip712.rb +184 -0
  27. data/lib/eth/key/decrypter.rb +121 -85
  28. data/lib/eth/key/encrypter.rb +180 -99
  29. data/lib/eth/key.rb +134 -45
  30. data/lib/eth/rlp/decoder.rb +114 -0
  31. data/lib/eth/rlp/encoder.rb +78 -0
  32. data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
  33. data/lib/eth/rlp/sedes/binary.rb +97 -0
  34. data/lib/eth/rlp/sedes/list.rb +84 -0
  35. data/lib/eth/rlp/sedes.rb +74 -0
  36. data/lib/eth/rlp.rb +63 -0
  37. data/lib/eth/signature.rb +163 -0
  38. data/lib/eth/solidity.rb +75 -0
  39. data/lib/eth/tx/eip1559.rb +337 -0
  40. data/lib/eth/tx/eip2930.rb +329 -0
  41. data/lib/eth/tx/legacy.rb +297 -0
  42. data/lib/eth/tx.rb +269 -146
  43. data/lib/eth/unit.rb +49 -0
  44. data/lib/eth/util.rb +235 -0
  45. data/lib/eth/version.rb +18 -1
  46. data/lib/eth.rb +34 -67
  47. metadata +47 -95
  48. data/.github/workflows/build.yml +0 -36
  49. data/lib/eth/gas.rb +0 -7
  50. data/lib/eth/open_ssl.rb +0 -395
  51. data/lib/eth/secp256k1.rb +0 -5
  52. data/lib/eth/sedes.rb +0 -39
  53. data/lib/eth/utils.rb +0 -126
@@ -0,0 +1,178 @@
1
+ # Copyright (c) 2016-2022 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 Applicatoin Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a class to handle and parse common ABI types.
24
+ class Type
25
+
26
+ # Provides a specific parser error if type cannot be determined.
27
+ class ParseError < StandardError; end
28
+
29
+ # The base attribute, e.g., `string` or `bytes`.
30
+ attr :base_type
31
+
32
+ # The sub-type attribute, e.g., `256` as size of an uint256.
33
+ attr :sub_type
34
+
35
+ # The dimension attribute, e.g., `[10]` for an array of size 10.
36
+ attr :dimensions
37
+
38
+ # Create a new Type object for base types, sub types, and dimensions.
39
+ # Should not be used; use {Type.parse} instead.
40
+ #
41
+ # @param base_type [String] the base-type attribute.
42
+ # @param sub_type [String] the sub-type attribute.
43
+ # @param dimensions [Array] the dimension attribute.
44
+ # @return [Eth::Abi::Type] an ABI type object.
45
+ def initialize(base_type, sub_type, dimensions)
46
+ sub_type = sub_type.to_s
47
+ @base_type = base_type
48
+ @sub_type = sub_type
49
+ @dimensions = dimensions
50
+ end
51
+
52
+ # Converts the self.parse method into a constructor.
53
+ konstructor :parse
54
+
55
+ # Attempts to parse a string containing a common Solidity type.
56
+ # Creates a new Type upon success (using konstructor).
57
+ #
58
+ # @param type [String] a common Solidity type.
59
+ # @return [Eth::Abi::Type] a parsed Type object.
60
+ # @raise [ParseError] if it fails to parse the type.
61
+ def parse(type)
62
+ _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
63
+
64
+ # type dimension can only be numeric
65
+ dims = dimension.scan(/\[[0-9]*\]/)
66
+ raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
67
+
68
+ # enforce base types
69
+ validate_base_type base_type, sub_type
70
+
71
+ # return a new Type (using konstructor)
72
+ sub_type = sub_type.to_s
73
+ @base_type = base_type
74
+ @sub_type = sub_type
75
+ @dimensions = dims.map { |x| x[1...-1].to_i }
76
+ end
77
+
78
+ # Creates a new uint256 type used for size.
79
+ #
80
+ # @return [Eth::Abi::Type] a uint256 size type.
81
+ def self.size_type
82
+ @size_type ||= new("uint", 256, [])
83
+ end
84
+
85
+ # Compares two types for their attributes.
86
+ #
87
+ # @param another_type [Eth::Abi::Type] another type to be compared.
88
+ # @return [Boolean] true if all attributes match.
89
+ def ==(another_type)
90
+ base_type == another_type.base_type and
91
+ sub_type == another_type.sub_type and
92
+ dimensions == another_type.dimensions
93
+ end
94
+
95
+ # Computes the size of a type if possible.
96
+ #
97
+ # @return [Integer] the size of the type; or nil if not available.
98
+ def size
99
+ s = nil
100
+ if dimensions.empty?
101
+ unless ["string", "bytes"].include?(base_type) and sub_type.empty?
102
+ s = 32
103
+ end
104
+ else
105
+ unless dimensions.last == 0
106
+ unless nested_sub.is_dynamic?
107
+ s = dimensions.last * nested_sub.size
108
+ end
109
+ end
110
+ end
111
+ @size ||= s
112
+ end
113
+
114
+ # Helpes to determine whether array is of dynamic size.
115
+ #
116
+ # @return [Boolean] true if array is of dynamic size.
117
+ def is_dynamic?
118
+ size.nil?
119
+ end
120
+
121
+ # Types can have nested sub-types in arrays.
122
+ #
123
+ # @return [Eth::Abi::Type] nested sub-type.
124
+ def nested_sub
125
+ @nested_sub ||= self.class.new(base_type, sub_type, dimensions[0...-1])
126
+ end
127
+
128
+ private
129
+
130
+ # Validates all known base types and raises if an issue occurs.
131
+ def validate_base_type(base_type, sub_type)
132
+ case base_type
133
+ when "string"
134
+
135
+ # string can not have any suffix
136
+ raise ParseError, "String type must have no suffix or numerical suffix" unless sub_type.empty?
137
+ when "bytes"
138
+
139
+ # bytes can be no longer than 32 bytes
140
+ raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub_type.empty? || sub_type.to_i <= 32
141
+ when "uint", "int"
142
+
143
+ # integers must have a numerical suffix
144
+ raise ParseError, "Integer type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
145
+
146
+ # integer size must be valid
147
+ size = sub_type.to_i
148
+ raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
149
+ raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
150
+ when "ureal", "real", "fixed", "ufixed"
151
+
152
+ # floats must have valid dimensional suffix
153
+ raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub_type =~ /\A[0-9]+x[0-9]+\z/
154
+ high, low = sub_type.split("x").map(&:to_i)
155
+ total = high + low
156
+ raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
157
+ raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
158
+ when "hash"
159
+
160
+ # hashs must have numerical suffix
161
+ raise ParseError, "Hash type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/
162
+ when "address"
163
+
164
+ # addresses cannot have any suffix
165
+ raise ParseError, "Address cannot have suffix" unless sub_type.empty?
166
+ when "bool"
167
+
168
+ # booleans cannot have any suffix
169
+ raise ParseError, "Bool cannot have suffix" unless sub_type.empty?
170
+ else
171
+
172
+ # we cannot parse arbitrary types such as 'decimal' or 'hex'
173
+ raise ParseError, "Unknown base type"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
data/lib/eth/abi.rb ADDED
@@ -0,0 +1,396 @@
1
+ # Copyright (c) 2016-2022 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
+ require "konstructor"
18
+
19
+ require "eth/abi/type"
20
+
21
+ # Provides the {Eth} module.
22
+ module Eth
23
+
24
+ # Provides a Ruby implementation of the Ethereum Applicatoin Binary Interface (ABI).
25
+ # ref: https://docs.soliditylang.org/en/develop/abi-spec.html
26
+ module Abi
27
+ extend self
28
+
29
+ # Provides a special encoding error if anything fails to encode.
30
+ class EncodingError < StandardError; end
31
+
32
+ # Provides a special decoding error if anything fails to decode.
33
+ class DecodingError < StandardError; end
34
+
35
+ # Provides a special out-of-bounds error for values.
36
+ class ValueOutOfBounds < StandardError; end
37
+
38
+ # Encodes Application Binary Interface (ABI) data. It accepts multiple
39
+ # arguments and encodes using the head/tail mechanism.
40
+ #
41
+ # @param types [Array] types to be ABI-encoded.
42
+ # @param args [Array] values to be ABI-encoded.
43
+ # @return [String] the encoded ABI data.
44
+ def encode(types, args)
45
+
46
+ # prase all types
47
+ parsed_types = types.map { |t| Type.parse(t) }
48
+
49
+ # prepare the "head"
50
+ head_size = (0...args.size)
51
+ .map { |i| parsed_types[i].size or 32 }
52
+ .reduce(0, &:+)
53
+ head, tail = "", ""
54
+
55
+ # encode types and arguments
56
+ args.each_with_index do |arg, i|
57
+ if parsed_types[i].is_dynamic?
58
+ head += encode_type Type.size_type, head_size + tail.size
59
+ tail += encode_type parsed_types[i], arg
60
+ else
61
+ head += encode_type parsed_types[i], arg
62
+ end
63
+ end
64
+
65
+ # return the encoded ABI blob
66
+ return "#{head}#{tail}"
67
+ end
68
+
69
+ # Encodes a specific value, either static or dynamic.
70
+ #
71
+ # @param type [Eth::Abi::Type] type to be encoded.
72
+ # @param arg [String|Number] value to be encoded.
73
+ # @return [String] the encoded type.
74
+ # @raise [EncodingError] if value does not match type.
75
+ def encode_type(type, arg)
76
+ if %w(string bytes).include? type.base_type and type.sub_type.empty?
77
+ raise EncodingError, "Argument must be a String" unless arg.instance_of? String
78
+
79
+ # encodes strings and bytes
80
+ size = encode_type Type.size_type, arg.size
81
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
82
+ return "#{size}#{arg}#{padding}"
83
+ elsif type.is_dynamic?
84
+ raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array
85
+
86
+ # encodes dynamic-sized arrays
87
+ head, tail = "", ""
88
+ head += encode_type Type.size_type, arg.size
89
+ nested_sub = type.nested_sub
90
+ nested_sub_size = type.nested_sub.size
91
+ arg.size.times do |i|
92
+
93
+ # ref https://github.com/ethereum/tests/issues/691
94
+ raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
95
+ head += encode_type nested_sub, arg[i]
96
+ end
97
+ return "#{head}#{tail}"
98
+ else
99
+ if type.dimensions.empty?
100
+
101
+ # encode a primitive type
102
+ return encode_primitive_type type, arg
103
+ else
104
+
105
+ # encode static-size arrays
106
+ return arg.map { |x| encode_type(type.nested_sub, x) }.join
107
+ end
108
+ end
109
+ end
110
+
111
+ # Encodes primitive types.
112
+ #
113
+ # @param type [Eth::Abi::Type] type to be encoded.
114
+ # @param arg [String|Number] value to be encoded.
115
+ # @return [String] the encoded primitive type.
116
+ # @raise [EncodingError] if value does not match type.
117
+ # @raise [ValueOutOfBounds] if value is out of bounds for type.
118
+ # @raise [EncodingError] if encoding fails for type.
119
+ def encode_primitive_type(type, arg)
120
+ case type.base_type
121
+ when "uint"
122
+ return encode_uint arg, type
123
+ when "bool"
124
+ return encode_bool arg
125
+ when "int"
126
+ return encode_int arg, type
127
+ when "ureal", "ufixed"
128
+ return encode_ufixed arg, type
129
+ when "real", "fixed"
130
+ return encode_fixed arg, type
131
+ when "string", "bytes"
132
+ return encode_bytes arg, type
133
+ when "hash"
134
+ return encode_hash arg, type
135
+ when "address"
136
+ return encode_address arg
137
+ else
138
+ raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}"
139
+ end
140
+ end
141
+
142
+ # Decodes Application Binary Interface (ABI) data. It accepts multiple
143
+ # arguments and decodes using the head/tail mechanism.
144
+ #
145
+ # @param types [Array] the ABI to be decoded.
146
+ # @param data [String] ABI data to be decoded.
147
+ # @return [Array] the decoded ABI data.
148
+ def decode(types, data)
149
+
150
+ # accept hex abi but decode it first
151
+ data = Util.hex_to_bin data if Util.is_hex? data
152
+
153
+ # parse all types
154
+ parsed_types = types.map { |t| Type.parse(t) }
155
+
156
+ # prepare output data
157
+ outputs = [nil] * types.size
158
+ start_positions = [nil] * types.size + [data.size]
159
+ pos = 0
160
+ parsed_types.each_with_index do |t, i|
161
+ if t.is_dynamic?
162
+
163
+ # record start position for dynamic type
164
+ start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32])
165
+ j = i - 1
166
+ while j >= 0 and start_positions[j].nil?
167
+ start_positions[j] = start_positions[i]
168
+ j -= 1
169
+ end
170
+ pos += 32
171
+ else
172
+
173
+ # get data directly for static types
174
+ outputs[i] = data[pos, t.size]
175
+ pos += t.size
176
+ end
177
+ end
178
+
179
+ # add start position equal the length of the entire data
180
+ j = types.size - 1
181
+ while j >= 0 and start_positions[j].nil?
182
+ start_positions[j] = start_positions[types.size]
183
+ j -= 1
184
+ end
185
+ raise DecodingError, "Not enough data for head" unless pos <= data.size
186
+
187
+ # add dynamic types
188
+ parsed_types.each_with_index do |t, i|
189
+ if t.is_dynamic?
190
+ offset, next_offset = start_positions[i, 2]
191
+ outputs[i] = data[offset...next_offset]
192
+ end
193
+ end
194
+
195
+ # return the decoded ABI types and data
196
+ return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) }
197
+ end
198
+
199
+ # Decodes a specific value, either static or dynamic.
200
+ #
201
+ # @param type [Eth::Abi::Type] type to be decoded.
202
+ # @param arg [String] encoded type data string.
203
+ # @return [String] the decoded data for the type.
204
+ # @raise [DecodingError] if decoding fails for type.
205
+ def decode_type(type, arg)
206
+ if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
207
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
208
+ data = arg[32..-1]
209
+ raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l)
210
+
211
+ # decoded strings and bytes
212
+ return data[0, l]
213
+ elsif type.is_dynamic?
214
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
215
+ nested_sub = type.nested_sub
216
+
217
+ # ref https://github.com/ethereum/tests/issues/691
218
+ raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
219
+
220
+ # decoded dynamic-sized arrays
221
+ return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
222
+ elsif !type.dimensions.empty?
223
+ l = type.dimensions.last[0]
224
+ nested_sub = type.nested_sub
225
+
226
+ # decoded static-size arrays
227
+ return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
228
+ else
229
+
230
+ # decoded primitive types
231
+ return decode_primitive_type type, arg
232
+ end
233
+ end
234
+
235
+ # Decodes primitive types.
236
+ #
237
+ # @param type [Eth::Abi::Type] type to be decoded.
238
+ # @param data [String] encoded primitive type data string.
239
+ # @return [String] the decoded data for the type.
240
+ # @raise [DecodingError] if decoding fails for type.
241
+ def decode_primitive_type(type, data)
242
+ case type.base_type
243
+ when "address"
244
+
245
+ # decoded address with 0x-prefix
246
+ return "0x#{Util.bin_to_hex data[12..-1]}"
247
+ when "string", "bytes"
248
+ if type.sub_type.empty?
249
+ size = Util.deserialize_big_endian_to_int data[0, 32]
250
+
251
+ # decoded dynamic-sized array
252
+ return data[32..-1][0, size]
253
+ else
254
+
255
+ # decoded static-sized array
256
+ return data[0, type.sub_type.to_i]
257
+ end
258
+ when "hash"
259
+
260
+ # decoded hash
261
+ return data[(32 - type.sub_type.to_i), type.sub_type.to_i]
262
+ when "uint"
263
+
264
+ # decoded unsigned integer
265
+ return Util.deserialize_big_endian_to_int data
266
+ when "int"
267
+ u = Util.deserialize_big_endian_to_int data
268
+ i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u
269
+
270
+ # decoded integer
271
+ return i
272
+ when "ureal", "ufixed"
273
+ high, low = type.sub_type.split("x").map(&:to_i)
274
+
275
+ # decoded unsigned fixed point numeric
276
+ return Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
277
+ when "real", "fixed"
278
+ high, low = type.sub_type.split("x").map(&:to_i)
279
+ u = Util.deserialize_big_endian_to_int data
280
+ i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
281
+
282
+ # decoded fixed point numeric
283
+ return i * 1.0 / 2 ** low
284
+ when "bool"
285
+
286
+ # decoded boolean
287
+ return data[-1] == Constant::BYTE_ONE
288
+ else
289
+ raise DecodingError, "Unknown primitive type: #{type.base_type}"
290
+ end
291
+ end
292
+
293
+ private
294
+
295
+ # Properly encodes unsigned integers.
296
+ def encode_uint(arg, type)
297
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN
298
+ real_size = type.sub_type.to_i
299
+ i = arg.to_i
300
+ raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size
301
+ return Util.zpad_int i
302
+ end
303
+
304
+ # Properly encodes signed integers.
305
+ def encode_int(arg, type)
306
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN
307
+ real_size = type.sub_type.to_i
308
+ i = arg.to_i
309
+ raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1)
310
+ return Util.zpad_int(i % 2 ** type.sub_type.to_i)
311
+ end
312
+
313
+ # Properly encodes booleans.
314
+ def encode_bool(arg)
315
+ raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass
316
+ return Util.zpad_int(arg ? 1 : 0)
317
+ end
318
+
319
+ # Properly encodes unsigned fixed-point numbers.
320
+ def encode_ufixed(arg, type)
321
+ high, low = type.sub_type.split("x").map(&:to_i)
322
+ raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
323
+ return Util.zpad_int((arg * 2 ** low).to_i)
324
+ end
325
+
326
+ # Properly encodes signed fixed-point numbers.
327
+ def encode_fixed(arg, type)
328
+ high, low = type.sub_type.split("x").map(&:to_i)
329
+ raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
330
+ i = (arg * 2 ** low).to_i
331
+ return Util.zpad_int(i % 2 ** (high + low))
332
+ end
333
+
334
+ # Properly encodes byte-strings.
335
+ def encode_bytes(arg, type)
336
+ raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String
337
+ if type.sub_type.empty?
338
+ size = Util.zpad_int arg.size
339
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
340
+
341
+ # variable length string/bytes
342
+ return "#{size}#{arg}#{padding}"
343
+ else
344
+ raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i
345
+ padding = Constant::BYTE_ZERO * (32 - arg.size)
346
+
347
+ # fixed length string/bytes
348
+ return "#{arg}#{padding}"
349
+ end
350
+ end
351
+
352
+ # Properly encodes hash-strings.
353
+ def encode_hash(arg, type)
354
+ size = type.sub_type.to_i
355
+ raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32
356
+ if arg.is_a? Integer
357
+
358
+ # hash from integer
359
+ return Util.zpad_int arg
360
+ elsif arg.size == size
361
+
362
+ # hash from encoded hash
363
+ return Util.zpad arg, 32
364
+ elsif arg.size == size * 2
365
+
366
+ # hash from hexa-decimal hash
367
+ return Util.zpad_hex arg
368
+ else
369
+ raise EncodingError, "Could not parse hash: #{arg}"
370
+ end
371
+ end
372
+
373
+ # Properly encodes addresses.
374
+ def encode_address(arg)
375
+ if arg.is_a? Integer
376
+
377
+ # address from integer
378
+ return Util.zpad_int arg
379
+ elsif arg.size == 20
380
+
381
+ # address from encoded address
382
+ return Util.zpad arg, 32
383
+ elsif arg.size == 40
384
+
385
+ # address from hexa-decimal address with 0x prefix
386
+ return Util.zpad_hex arg
387
+ elsif arg.size == 42 and arg[0, 2] == "0x"
388
+
389
+ # address from hexa-decimal address
390
+ return Util.zpad_hex arg[2..-1]
391
+ else
392
+ raise EncodingError, "Could not parse address: #{arg}"
393
+ end
394
+ end
395
+ end
396
+ end
data/lib/eth/address.rb CHANGED
@@ -1,9 +1,46 @@
1
+ # Copyright (c) 2016-2022 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
+ # Provides the {Eth} module.
1
16
  module Eth
17
+
18
+ # The {Eth::Address} class to handle checksummed Ethereum addresses.
2
19
  class Address
20
+
21
+ # Provides a special checksum error if EIP-55 is violated.
22
+ class CheckSumError < StandardError; end
23
+
24
+ # The prefixed and checksummed Ethereum address.
25
+ attr_reader :address
26
+
27
+ # Constructor of the {Eth::Address} class. Creates a new hex
28
+ # prefixed address.
29
+ #
30
+ # @param address [String] hex string representing an ethereum address.
3
31
  def initialize(address)
4
- @address = Utils.prefix_hex(address)
32
+ unless Util.is_hex? address
33
+ raise CheckSumError, "Unknown address type #{address}!"
34
+ end
35
+ @address = Util.prefix_hex address
36
+ unless self.valid?
37
+ raise CheckSumError, "Invalid address provided #{address}"
38
+ end
5
39
  end
6
40
 
41
+ # Checks that the address is valid.
42
+ #
43
+ # @return [Boolean] true if valid address.
7
44
  def valid?
8
45
  if !matches_any_format?
9
46
  false
@@ -14,46 +51,56 @@ module Eth
14
51
  end
15
52
  end
16
53
 
54
+ # Generate a checksummed address.
55
+ #
56
+ # @return [String] prefixed hexstring representing an checksummed address.
17
57
  def checksummed
18
- raise "Invalid address: #{address}" unless matches_any_format?
58
+ raise CheckSumError, "Invalid address: #{address}" unless matches_any_format?
19
59
 
20
60
  cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
21
61
  check.match(/[0-7]/) ? char.downcase : char.upcase
22
62
  end
23
63
 
24
- Utils.prefix_hex(cased.join)
64
+ Util.prefix_hex cased.join
25
65
  end
26
66
 
27
- private
67
+ alias :to_s :checksummed
28
68
 
29
- attr_reader :address
69
+ private
30
70
 
71
+ # Checks whether the address checksum matches.
31
72
  def checksum_matches?
32
73
  address == checksummed
33
74
  end
34
75
 
76
+ # Checks whether the address is not checksummed.
35
77
  def not_checksummed?
36
78
  all_uppercase? || all_lowercase?
37
79
  end
38
80
 
81
+ # Checks whether the address is all upper-case.
39
82
  def all_uppercase?
40
- address.match(/(?:0[xX])[A-F0-9]{40}/)
83
+ address.match /(?:0[xX])[A-F0-9]{40}/
41
84
  end
42
85
 
86
+ # Checks whether the address is all lower-case.
43
87
  def all_lowercase?
44
- address.match(/(?:0[xX])[a-f0-9]{40}/)
88
+ address.match /(?:0[xX])[a-f0-9]{40}/
45
89
  end
46
90
 
91
+ # Checks whether the address matches any known format.
47
92
  def matches_any_format?
48
- address.match(/\A(?:0[xX])[a-fA-F0-9]{40}\z/)
93
+ address.match /\A(?:0[xX])[a-fA-F0-9]{40}\z/
49
94
  end
50
95
 
96
+ # Computes the checksum of the address.
51
97
  def checksum
52
- Utils.bin_to_hex(Utils.keccak256 unprefixed.downcase)
98
+ Util.bin_to_hex Util.keccak256 unprefixed.downcase
53
99
  end
54
100
 
101
+ # Removes the hex prefix.
55
102
  def unprefixed
56
- Utils.remove_hex_prefix address
103
+ Util.remove_hex_prefix address
57
104
  end
58
105
  end
59
106
  end