eth 0.4.16 → 0.5.1

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