eth 0.4.18 → 0.5.2

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