klay 0.0.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.
data/lib/klay/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 "klay/abi/type"
20
+
21
+ # Provides the {Eth} module.
22
+ module Klay
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
@@ -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 Klay
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