eth-custom 0.5.7
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.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/codeql.yml +48 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +52 -0
- data/.gitignore +43 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.yardopts +1 -0
- data/AUTHORS.txt +29 -0
- data/CHANGELOG.md +218 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +202 -0
- data/README.md +347 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/codecov.yml +6 -0
- data/eth.gemspec +51 -0
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +178 -0
- data/lib/eth/abi.rb +446 -0
- data/lib/eth/address.rb +106 -0
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +157 -0
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +499 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/contract/event.rb +42 -0
- data/lib/eth/contract/function.rb +57 -0
- data/lib/eth/contract/function_input.rb +38 -0
- data/lib/eth/contract/function_output.rb +37 -0
- data/lib/eth/contract/initializer.rb +47 -0
- data/lib/eth/contract.rb +143 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +146 -0
- data/lib/eth/key/encrypter.rb +207 -0
- data/lib/eth/key.rb +167 -0
- data/lib/eth/rlp/decoder.rb +114 -0
- data/lib/eth/rlp/encoder.rb +78 -0
- data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
- data/lib/eth/rlp/sedes/binary.rb +97 -0
- data/lib/eth/rlp/sedes/list.rb +84 -0
- data/lib/eth/rlp/sedes.rb +74 -0
- data/lib/eth/rlp.rb +63 -0
- data/lib/eth/signature.rb +163 -0
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +337 -0
- data/lib/eth/tx/eip2930.rb +329 -0
- data/lib/eth/tx/legacy.rb +297 -0
- data/lib/eth/tx.rb +322 -0
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +235 -0
- data/lib/eth/version.rb +20 -0
- data/lib/eth.rb +35 -0
- metadata +184 -0
data/lib/eth/abi.rb
ADDED
@@ -0,0 +1,446 @@
|
|
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/event"
|
20
|
+
require "eth/abi/type"
|
21
|
+
|
22
|
+
# Provides the {Eth} module.
|
23
|
+
module Eth
|
24
|
+
|
25
|
+
# Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
|
26
|
+
# ref: https://docs.soliditylang.org/en/develop/abi-spec.html
|
27
|
+
module Abi
|
28
|
+
extend self
|
29
|
+
|
30
|
+
# Provides a special encoding error if anything fails to encode.
|
31
|
+
class EncodingError < StandardError; end
|
32
|
+
|
33
|
+
# Provides a special decoding error if anything fails to decode.
|
34
|
+
class DecodingError < StandardError; end
|
35
|
+
|
36
|
+
# Provides a special out-of-bounds error for values.
|
37
|
+
class ValueOutOfBounds < StandardError; end
|
38
|
+
|
39
|
+
# Encodes Application Binary Interface (ABI) data. It accepts multiple
|
40
|
+
# arguments and encodes using the head/tail mechanism.
|
41
|
+
#
|
42
|
+
# @param types [Array] types to be ABI-encoded.
|
43
|
+
# @param args [Array] values to be ABI-encoded.
|
44
|
+
# @return [String] the encoded ABI data.
|
45
|
+
def encode(types, args)
|
46
|
+
|
47
|
+
# parse all types
|
48
|
+
parsed_types = types.map { |t| Type.parse(t) }
|
49
|
+
|
50
|
+
# prepare the "head"
|
51
|
+
head_size = (0...args.size)
|
52
|
+
.map { |i| parsed_types[i].size or 32 }
|
53
|
+
.reduce(0, &:+)
|
54
|
+
head, tail = "", ""
|
55
|
+
|
56
|
+
# encode types and arguments
|
57
|
+
args.each_with_index do |arg, i|
|
58
|
+
if parsed_types[i].is_dynamic?
|
59
|
+
head += encode_type Type.size_type, head_size + tail.size
|
60
|
+
tail += encode_type parsed_types[i], arg
|
61
|
+
else
|
62
|
+
head += encode_type parsed_types[i], arg
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# return the encoded ABI blob
|
67
|
+
return "#{head}#{tail}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Encodes a specific value, either static or dynamic.
|
71
|
+
#
|
72
|
+
# @param type [Eth::Abi::Type] type to be encoded.
|
73
|
+
# @param arg [String|Number] value to be encoded.
|
74
|
+
# @return [String] the encoded type.
|
75
|
+
# @raise [EncodingError] if value does not match type.
|
76
|
+
def encode_type(type, arg)
|
77
|
+
if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty?
|
78
|
+
raise EncodingError, "Argument must be a String" unless arg.instance_of? String
|
79
|
+
|
80
|
+
# encodes strings and bytes
|
81
|
+
size = encode_type Type.size_type, arg.size
|
82
|
+
padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
|
83
|
+
return "#{size}#{arg}#{padding}"
|
84
|
+
elsif type.is_dynamic?
|
85
|
+
raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array
|
86
|
+
|
87
|
+
# encodes dynamic-sized arrays
|
88
|
+
head, tail = "", ""
|
89
|
+
head += encode_type Type.size_type, arg.size
|
90
|
+
nested_sub = type.nested_sub
|
91
|
+
nested_sub_size = type.nested_sub.size
|
92
|
+
|
93
|
+
# calculate offsets
|
94
|
+
if %w(string bytes).include?(type.base_type) && type.sub_type.empty?
|
95
|
+
offset = 0
|
96
|
+
arg.size.times do |i|
|
97
|
+
if i == 0
|
98
|
+
offset = arg.size * 32
|
99
|
+
else
|
100
|
+
number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
|
101
|
+
total_bytes_length = number_of_words * 32
|
102
|
+
offset += total_bytes_length + 32
|
103
|
+
end
|
104
|
+
|
105
|
+
head += encode_type Type.size_type, offset
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
arg.size.times do |i|
|
110
|
+
head += encode_type nested_sub, arg[i]
|
111
|
+
end
|
112
|
+
return "#{head}#{tail}"
|
113
|
+
else
|
114
|
+
if type.dimensions.empty?
|
115
|
+
|
116
|
+
# encode a primitive type
|
117
|
+
return encode_primitive_type type, arg
|
118
|
+
else
|
119
|
+
|
120
|
+
# encode static-size arrays
|
121
|
+
return arg.map { |x| encode_type(type.nested_sub, x) }.join
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Encodes primitive types.
|
127
|
+
#
|
128
|
+
# @param type [Eth::Abi::Type] type to be encoded.
|
129
|
+
# @param arg [String|Number] value to be encoded.
|
130
|
+
# @return [String] the encoded primitive type.
|
131
|
+
# @raise [EncodingError] if value does not match type.
|
132
|
+
# @raise [ValueOutOfBounds] if value is out of bounds for type.
|
133
|
+
# @raise [EncodingError] if encoding fails for type.
|
134
|
+
def encode_primitive_type(type, arg)
|
135
|
+
case type.base_type
|
136
|
+
when "uint"
|
137
|
+
return encode_uint arg, type
|
138
|
+
when "bool"
|
139
|
+
return encode_bool arg
|
140
|
+
when "int"
|
141
|
+
return encode_int arg, type
|
142
|
+
when "ureal", "ufixed"
|
143
|
+
return encode_ufixed arg, type
|
144
|
+
when "real", "fixed"
|
145
|
+
return encode_fixed arg, type
|
146
|
+
when "string", "bytes"
|
147
|
+
return encode_bytes arg, type
|
148
|
+
when "hash"
|
149
|
+
return encode_hash arg, type
|
150
|
+
when "address"
|
151
|
+
return encode_address arg
|
152
|
+
else
|
153
|
+
raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Decodes Application Binary Interface (ABI) data. It accepts multiple
|
158
|
+
# arguments and decodes using the head/tail mechanism.
|
159
|
+
#
|
160
|
+
# @param types [Array] the ABI to be decoded.
|
161
|
+
# @param data [String] ABI data to be decoded.
|
162
|
+
# @return [Array] the decoded ABI data.
|
163
|
+
def decode(types, data)
|
164
|
+
|
165
|
+
# accept hex abi but decode it first
|
166
|
+
data = Util.hex_to_bin data if Util.is_hex? data
|
167
|
+
|
168
|
+
# parse all types
|
169
|
+
parsed_types = types.map { |t| Type.parse(t) }
|
170
|
+
|
171
|
+
# prepare output data
|
172
|
+
outputs = [nil] * types.size
|
173
|
+
start_positions = [nil] * types.size + [data.size]
|
174
|
+
pos = 0
|
175
|
+
parsed_types.each_with_index do |t, i|
|
176
|
+
if t.is_dynamic?
|
177
|
+
|
178
|
+
# record start position for dynamic type
|
179
|
+
start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32])
|
180
|
+
j = i - 1
|
181
|
+
while j >= 0 and start_positions[j].nil?
|
182
|
+
start_positions[j] = start_positions[i]
|
183
|
+
j -= 1
|
184
|
+
end
|
185
|
+
pos += 32
|
186
|
+
else
|
187
|
+
|
188
|
+
# get data directly for static types
|
189
|
+
outputs[i] = data[pos, t.size]
|
190
|
+
pos += t.size
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# add start position equal the length of the entire data
|
195
|
+
j = types.size - 1
|
196
|
+
while j >= 0 and start_positions[j].nil?
|
197
|
+
start_positions[j] = start_positions[types.size]
|
198
|
+
j -= 1
|
199
|
+
end
|
200
|
+
raise DecodingError, "Not enough data for head" unless pos <= data.size
|
201
|
+
|
202
|
+
# add dynamic types
|
203
|
+
parsed_types.each_with_index do |t, i|
|
204
|
+
if t.is_dynamic?
|
205
|
+
offset, next_offset = start_positions[i, 2]
|
206
|
+
outputs[i] = data[offset...next_offset]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# return the decoded ABI types and data
|
211
|
+
return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) }
|
212
|
+
end
|
213
|
+
|
214
|
+
# Decodes a specific value, either static or dynamic.
|
215
|
+
#
|
216
|
+
# @param type [Eth::Abi::Type] type to be decoded.
|
217
|
+
# @param arg [String] encoded type data string.
|
218
|
+
# @return [String] the decoded data for the type.
|
219
|
+
# @raise [DecodingError] if decoding fails for type.
|
220
|
+
def decode_type(type, arg)
|
221
|
+
if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
|
222
|
+
l = Util.deserialize_big_endian_to_int arg[0, 32]
|
223
|
+
data = arg[32..-1]
|
224
|
+
raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l)
|
225
|
+
|
226
|
+
# decoded strings and bytes
|
227
|
+
return data[0, l]
|
228
|
+
elsif type.is_dynamic?
|
229
|
+
l = Util.deserialize_big_endian_to_int arg[0, 32]
|
230
|
+
nested_sub = type.nested_sub
|
231
|
+
|
232
|
+
# ref https://github.com/ethereum/tests/issues/691
|
233
|
+
raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
|
234
|
+
|
235
|
+
# decoded dynamic-sized arrays
|
236
|
+
return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
|
237
|
+
elsif !type.dimensions.empty?
|
238
|
+
l = type.dimensions.last[0]
|
239
|
+
nested_sub = type.nested_sub
|
240
|
+
|
241
|
+
# decoded static-size arrays
|
242
|
+
return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
|
243
|
+
else
|
244
|
+
|
245
|
+
# decoded primitive types
|
246
|
+
return decode_primitive_type type, arg
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Decodes primitive types.
|
251
|
+
#
|
252
|
+
# @param type [Eth::Abi::Type] type to be decoded.
|
253
|
+
# @param data [String] encoded primitive type data string.
|
254
|
+
# @return [String] the decoded data for the type.
|
255
|
+
# @raise [DecodingError] if decoding fails for type.
|
256
|
+
def decode_primitive_type(type, data)
|
257
|
+
case type.base_type
|
258
|
+
when "address"
|
259
|
+
|
260
|
+
# decoded address with 0x-prefix
|
261
|
+
return "0x#{Util.bin_to_hex data[12..-1]}"
|
262
|
+
when "string", "bytes"
|
263
|
+
if type.sub_type.empty?
|
264
|
+
size = Util.deserialize_big_endian_to_int data[0, 32]
|
265
|
+
|
266
|
+
# decoded dynamic-sized array
|
267
|
+
return data[32..-1][0, size]
|
268
|
+
else
|
269
|
+
|
270
|
+
# decoded static-sized array
|
271
|
+
return data[0, type.sub_type.to_i]
|
272
|
+
end
|
273
|
+
when "hash"
|
274
|
+
|
275
|
+
# decoded hash
|
276
|
+
return data[(32 - type.sub_type.to_i), type.sub_type.to_i]
|
277
|
+
when "uint"
|
278
|
+
|
279
|
+
# decoded unsigned integer
|
280
|
+
return Util.deserialize_big_endian_to_int data
|
281
|
+
when "int"
|
282
|
+
u = Util.deserialize_big_endian_to_int data
|
283
|
+
i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u
|
284
|
+
|
285
|
+
# decoded integer
|
286
|
+
return i
|
287
|
+
when "ureal", "ufixed"
|
288
|
+
high, low = type.sub_type.split("x").map(&:to_i)
|
289
|
+
|
290
|
+
# decoded unsigned fixed point numeric
|
291
|
+
return Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
|
292
|
+
when "real", "fixed"
|
293
|
+
high, low = type.sub_type.split("x").map(&:to_i)
|
294
|
+
u = Util.deserialize_big_endian_to_int data
|
295
|
+
i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
|
296
|
+
|
297
|
+
# decoded fixed point numeric
|
298
|
+
return i * 1.0 / 2 ** low
|
299
|
+
when "bool"
|
300
|
+
|
301
|
+
# decoded boolean
|
302
|
+
return data[-1] == Constant::BYTE_ONE
|
303
|
+
else
|
304
|
+
raise DecodingError, "Unknown primitive type: #{type.base_type}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Build event signature string from ABI interface.
|
309
|
+
#
|
310
|
+
# @param interface [Hash] ABI event interface.
|
311
|
+
# @return [String] interface signature string.
|
312
|
+
def signature(interface)
|
313
|
+
name = interface.fetch("name")
|
314
|
+
inputs = interface.fetch("inputs", [])
|
315
|
+
types = inputs.map { |i| i.fetch("type") }
|
316
|
+
"#{name}(#{types.join(",")})"
|
317
|
+
end
|
318
|
+
|
319
|
+
private
|
320
|
+
|
321
|
+
# Properly encodes unsigned integers.
|
322
|
+
def encode_uint(arg, type)
|
323
|
+
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
324
|
+
raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN
|
325
|
+
real_size = type.sub_type.to_i
|
326
|
+
i = arg.to_i
|
327
|
+
raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size
|
328
|
+
return Util.zpad_int i
|
329
|
+
end
|
330
|
+
|
331
|
+
# Properly encodes signed integers.
|
332
|
+
def encode_int(arg, type)
|
333
|
+
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
334
|
+
raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN
|
335
|
+
real_size = type.sub_type.to_i
|
336
|
+
i = arg.to_i
|
337
|
+
raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1)
|
338
|
+
return Util.zpad_int(i % 2 ** type.sub_type.to_i)
|
339
|
+
end
|
340
|
+
|
341
|
+
# Properly encodes booleans.
|
342
|
+
def encode_bool(arg)
|
343
|
+
raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass
|
344
|
+
return Util.zpad_int(arg ? 1 : 0)
|
345
|
+
end
|
346
|
+
|
347
|
+
# Properly encodes unsigned fixed-point numbers.
|
348
|
+
def encode_ufixed(arg, type)
|
349
|
+
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
350
|
+
high, low = type.sub_type.split("x").map(&:to_i)
|
351
|
+
raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
|
352
|
+
return Util.zpad_int((arg * 2 ** low).to_i)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Properly encodes signed fixed-point numbers.
|
356
|
+
def encode_fixed(arg, type)
|
357
|
+
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
358
|
+
high, low = type.sub_type.split("x").map(&:to_i)
|
359
|
+
raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
|
360
|
+
i = (arg * 2 ** low).to_i
|
361
|
+
return Util.zpad_int(i % 2 ** (high + low))
|
362
|
+
end
|
363
|
+
|
364
|
+
# Properly encodes byte-strings.
|
365
|
+
def encode_bytes(arg, type)
|
366
|
+
raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String
|
367
|
+
arg = handle_hex_string arg, type
|
368
|
+
|
369
|
+
if type.sub_type.empty?
|
370
|
+
size = Util.zpad_int arg.size
|
371
|
+
padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
|
372
|
+
|
373
|
+
# variable length string/bytes
|
374
|
+
return "#{size}#{arg}#{padding}"
|
375
|
+
else
|
376
|
+
raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i
|
377
|
+
padding = Constant::BYTE_ZERO * (32 - arg.size)
|
378
|
+
|
379
|
+
# fixed length string/bytes
|
380
|
+
return "#{arg}#{padding}"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Properly encodes hash-strings.
|
385
|
+
def encode_hash(arg, type)
|
386
|
+
size = type.sub_type.to_i
|
387
|
+
raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32
|
388
|
+
if arg.is_a? Integer
|
389
|
+
|
390
|
+
# hash from integer
|
391
|
+
return Util.zpad_int arg
|
392
|
+
elsif arg.size == size
|
393
|
+
|
394
|
+
# hash from encoded hash
|
395
|
+
return Util.zpad arg, 32
|
396
|
+
elsif arg.size == size * 2
|
397
|
+
|
398
|
+
# hash from hexa-decimal hash
|
399
|
+
return Util.zpad_hex arg
|
400
|
+
else
|
401
|
+
raise EncodingError, "Could not parse hash: #{arg}"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Properly encodes addresses.
|
406
|
+
def encode_address(arg)
|
407
|
+
if arg.is_a? Integer
|
408
|
+
|
409
|
+
# address from integer
|
410
|
+
return Util.zpad_int arg
|
411
|
+
elsif arg.size == 20
|
412
|
+
|
413
|
+
# address from encoded address
|
414
|
+
return Util.zpad arg, 32
|
415
|
+
elsif arg.size == 40
|
416
|
+
|
417
|
+
# address from hexa-decimal address with 0x prefix
|
418
|
+
return Util.zpad_hex arg
|
419
|
+
elsif arg.size == 42 and arg[0, 2] == "0x"
|
420
|
+
|
421
|
+
# address from hexa-decimal address
|
422
|
+
return Util.zpad_hex arg[2..-1]
|
423
|
+
else
|
424
|
+
raise EncodingError, "Could not parse address: #{arg}"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# The ABI encoder needs to be able to determine between a hex `"123"`
|
429
|
+
# and a binary `"123"` string.
|
430
|
+
def handle_hex_string(arg, type)
|
431
|
+
if Util.is_prefixed? arg or
|
432
|
+
(arg.size === type.sub_type.to_i * 2 and Util.is_hex? arg)
|
433
|
+
|
434
|
+
# There is no way telling whether a string is hex or binary with certainty
|
435
|
+
# in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string.
|
436
|
+
# Additionally, if the string size is exactly the double of the expected
|
437
|
+
# binary size, we can assume a hex value.
|
438
|
+
return Util.hex_to_bin arg
|
439
|
+
else
|
440
|
+
|
441
|
+
# Everything else will be assumed binary or raw string.
|
442
|
+
return arg.b
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
data/lib/eth/address.rb
ADDED
@@ -0,0 +1,106 @@
|
|
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.
|
16
|
+
module Eth
|
17
|
+
|
18
|
+
# The {Eth::Address} class to handle checksummed Ethereum addresses.
|
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.
|
31
|
+
def initialize(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
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks that the address is valid.
|
42
|
+
#
|
43
|
+
# @return [Boolean] true if valid address.
|
44
|
+
def valid?
|
45
|
+
if !matches_any_format?
|
46
|
+
false
|
47
|
+
elsif not_checksummed?
|
48
|
+
true
|
49
|
+
else
|
50
|
+
checksum_matches?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate a checksummed address.
|
55
|
+
#
|
56
|
+
# @return [String] prefixed hexstring representing an checksummed address.
|
57
|
+
def checksummed
|
58
|
+
raise CheckSumError, "Invalid address: #{address}" unless matches_any_format?
|
59
|
+
|
60
|
+
cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
|
61
|
+
check.match(/[0-7]/) ? char.downcase : char.upcase
|
62
|
+
end
|
63
|
+
|
64
|
+
Util.prefix_hex cased.join
|
65
|
+
end
|
66
|
+
|
67
|
+
alias :to_s :checksummed
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Checks whether the address checksum matches.
|
72
|
+
def checksum_matches?
|
73
|
+
address == checksummed
|
74
|
+
end
|
75
|
+
|
76
|
+
# Checks whether the address is not checksummed.
|
77
|
+
def not_checksummed?
|
78
|
+
all_uppercase? || all_lowercase?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks whether the address is all upper-case.
|
82
|
+
def all_uppercase?
|
83
|
+
address.match /(?:0[xX])[A-F0-9]{40}/
|
84
|
+
end
|
85
|
+
|
86
|
+
# Checks whether the address is all lower-case.
|
87
|
+
def all_lowercase?
|
88
|
+
address.match /(?:0[xX])[a-f0-9]{40}/
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks whether the address matches any known format.
|
92
|
+
def matches_any_format?
|
93
|
+
address.match /\A(?:0[xX])[a-fA-F0-9]{40}\z/
|
94
|
+
end
|
95
|
+
|
96
|
+
# Computes the checksum of the address.
|
97
|
+
def checksum
|
98
|
+
Util.bin_to_hex Util.keccak256 unprefixed.downcase
|
99
|
+
end
|
100
|
+
|
101
|
+
# Removes the hex prefix.
|
102
|
+
def unprefixed
|
103
|
+
Util.remove_hex_prefix address
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|