eth 0.4.16 → 0.5.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +15 -5
  3. data/.github/workflows/docs.yml +26 -0
  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 +21 -0
  9. data/CHANGELOG.md +65 -10
  10. data/Gemfile +13 -2
  11. data/LICENSE.txt +202 -22
  12. data/README.md +199 -74
  13. data/bin/console +4 -4
  14. data/bin/setup +5 -4
  15. data/eth.gemspec +34 -29
  16. data/lib/eth/abi/type.rb +178 -0
  17. data/lib/eth/abi.rb +396 -0
  18. data/lib/eth/address.rb +55 -11
  19. data/lib/eth/api.rb +223 -0
  20. data/lib/eth/chain.rb +151 -0
  21. data/lib/eth/client/http.rb +63 -0
  22. data/lib/eth/client/ipc.rb +47 -0
  23. data/lib/eth/client.rb +232 -0
  24. data/lib/eth/constant.rb +71 -0
  25. data/lib/eth/eip712.rb +184 -0
  26. data/lib/eth/key/decrypter.rb +121 -88
  27. data/lib/eth/key/encrypter.rb +178 -99
  28. data/lib/eth/key.rb +136 -48
  29. data/lib/eth/rlp/decoder.rb +109 -0
  30. data/lib/eth/rlp/encoder.rb +78 -0
  31. data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
  32. data/lib/eth/rlp/sedes/binary.rb +97 -0
  33. data/lib/eth/rlp/sedes/list.rb +84 -0
  34. data/lib/eth/rlp/sedes.rb +74 -0
  35. data/lib/eth/rlp.rb +63 -0
  36. data/lib/eth/signature.rb +163 -0
  37. data/lib/eth/tx/eip1559.rb +336 -0
  38. data/lib/eth/tx/eip2930.rb +328 -0
  39. data/lib/eth/tx/legacy.rb +296 -0
  40. data/lib/eth/tx.rb +273 -143
  41. data/lib/eth/unit.rb +49 -0
  42. data/lib/eth/util.rb +235 -0
  43. data/lib/eth/version.rb +18 -1
  44. data/lib/eth.rb +33 -67
  45. metadata +50 -85
  46. data/.github/workflows/build.yml +0 -26
  47. data/lib/eth/gas.rb +0 -9
  48. data/lib/eth/open_ssl.rb +0 -264
  49. data/lib/eth/secp256k1.rb +0 -7
  50. data/lib/eth/sedes.rb +0 -40
  51. data/lib/eth/utils.rb +0 -130
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,10 +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
3
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.
4
31
  def initialize(address)
5
- @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
6
39
  end
7
40
 
41
+ # Checks that the address is valid.
42
+ #
43
+ # @return [Boolean] true if valid address.
8
44
  def valid?
9
45
  if !matches_any_format?
10
46
  false
@@ -15,48 +51,56 @@ module Eth
15
51
  end
16
52
  end
17
53
 
54
+ # Generate a checksummed address.
55
+ #
56
+ # @return [String] prefixed hexstring representing an checksummed address.
18
57
  def checksummed
19
- raise "Invalid address: #{address}" unless matches_any_format?
58
+ raise CheckSumError, "Invalid address: #{address}" unless matches_any_format?
20
59
 
21
60
  cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
22
61
  check.match(/[0-7]/) ? char.downcase : char.upcase
23
62
  end
24
63
 
25
- Utils.prefix_hex(cased.join)
64
+ Util.prefix_hex cased.join
26
65
  end
27
66
 
67
+ alias :to_s :checksummed
28
68
 
29
69
  private
30
70
 
31
- attr_reader :address
32
-
71
+ # Checks whether the address checksum matches.
33
72
  def checksum_matches?
34
73
  address == checksummed
35
74
  end
36
75
 
76
+ # Checks whether the address is not checksummed.
37
77
  def not_checksummed?
38
78
  all_uppercase? || all_lowercase?
39
79
  end
40
80
 
81
+ # Checks whether the address is all upper-case.
41
82
  def all_uppercase?
42
- address.match(/(?:0[xX])[A-F0-9]{40}/)
83
+ address.match /(?:0[xX])[A-F0-9]{40}/
43
84
  end
44
85
 
86
+ # Checks whether the address is all lower-case.
45
87
  def all_lowercase?
46
- address.match(/(?:0[xX])[a-f0-9]{40}/)
88
+ address.match /(?:0[xX])[a-f0-9]{40}/
47
89
  end
48
90
 
91
+ # Checks whether the address matches any known format.
49
92
  def matches_any_format?
50
- address.match(/\A(?:0[xX])[a-fA-F0-9]{40}\z/)
93
+ address.match /\A(?:0[xX])[a-fA-F0-9]{40}\z/
51
94
  end
52
95
 
96
+ # Computes the checksum of the address.
53
97
  def checksum
54
- Utils.bin_to_hex(Utils.keccak256 unprefixed.downcase)
98
+ Util.bin_to_hex Util.keccak256 unprefixed.downcase
55
99
  end
56
100
 
101
+ # Removes the hex prefix.
57
102
  def unprefixed
58
- Utils.remove_hex_prefix address
103
+ Util.remove_hex_prefix address
59
104
  end
60
-
61
105
  end
62
106
  end