ethlite 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 782c6115c725eaefb922c2486832197ddeb44473d21d74e9b8f27de9986aef63
4
- data.tar.gz: 30db801b21cc0fb703ba78908470b5e6ea9b2741768b03caf7a01bd7a772734a
3
+ metadata.gz: 9ee22cabcf4a413047cd733de839213fefe59ea3a45ddc353f9dd7675f416e28
4
+ data.tar.gz: c16a6e278a4102061f6efbf7908171ab04653108b2abd4c9d8507ab7bba46324
5
5
  SHA512:
6
- metadata.gz: 3babcd1abc28ba676df94e7afe53cf3ea7880a4b69e112c85ab12b6f6d226b63424e5cebe7678e91b0c34f2ed4da6bbcf3e88eb401d9e3ac7375ef38592f4bbf
7
- data.tar.gz: 34c38f561121d5ad42070b2fbadc6899cc94b1e8ecf4a6f695f71a1a5166f98211fbe572a7a0b2d9d111407ef1b4fcf7058d2f58aca81d5e1e4662b834cd7f77
6
+ metadata.gz: 356dd2d7d825fe42d30e94d48b58d8a737ee0cd274e1329c9425341d953e0654ac1a34d789c5f98ca52706b3f1f0702a7d4a418d758a76856410ae72f1bc1b1a
7
+ data.tar.gz: 0512f202e3b4eefb7a2115158036cd4e11813ef37f0b4df3a9e8269b50f90d8c2fa8d6ae6cade9caf9a49a96b30556ef28e2afa5d8debfe8d2ebf95c221b64a0
data/Manifest.txt CHANGED
@@ -3,4 +3,12 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/ethlite.rb
6
+ lib/ethlite/abi/abi_coder.rb
7
+ lib/ethlite/abi/constant.rb
8
+ lib/ethlite/abi/exceptions.rb
9
+ lib/ethlite/abi/type.rb
10
+ lib/ethlite/abi/utils.rb
11
+ lib/ethlite/contract.rb
12
+ lib/ethlite/rpc.rb
13
+ lib/ethlite/utility.rb
6
14
  lib/ethlite/version.rb
data/Rakefile CHANGED
@@ -20,6 +20,8 @@ Hoe.spec 'ethlite' do
20
20
 
21
21
  self.extra_deps = [
22
22
  ['cocos'],
23
+ ['keccak'],
24
+ ['rlp'],
23
25
  ]
24
26
 
25
27
  self.licenses = ['Public Domain']
@@ -0,0 +1,423 @@
1
+
2
+
3
+ module Ethlite
4
+ module Abi
5
+
6
+ ##
7
+ # Contract ABI encoding and decoding.
8
+ #
9
+ # @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
10
+ #
11
+ module AbiCoder
12
+
13
+ extend self
14
+
15
+ include Constant
16
+
17
+ class EncodingError < StandardError; end
18
+ class DecodingError < StandardError; end
19
+ class ValueOutOfBounds < ValueError; end
20
+
21
+ ##
22
+ # Encodes multiple arguments using the head/tail mechanism.
23
+ #
24
+ def encode_abi(types, args)
25
+ parsed_types = types.map {|t| Type.parse(t) }
26
+
27
+ head_size = (0...args.size)
28
+ .map {|i| parsed_types[i].size || 32 }
29
+ .reduce(0, &:+)
30
+
31
+ head, tail = '', ''
32
+ args.each_with_index do |arg, i|
33
+ if parsed_types[i].dynamic?
34
+ head += encode_type(Type.size_type, head_size + tail.size)
35
+ tail += encode_type(parsed_types[i], arg)
36
+ else
37
+ head += encode_type(parsed_types[i], arg)
38
+ end
39
+ end
40
+
41
+ "#{head}#{tail}"
42
+ end
43
+ alias :encode :encode_abi
44
+
45
+ ##
46
+ # Encodes a single value (static or dynamic).
47
+ #
48
+ # @param type [Ethereum::ABI::Type] value type
49
+ # @param arg [Object] value
50
+ #
51
+ # @return [String] encoded bytes
52
+ #
53
+ def encode_type(type, arg)
54
+ if %w(string bytes).include?(type.base) && type.sub.empty?
55
+ encode_primitive_type type, arg
56
+ elsif type.dynamic?
57
+ raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
58
+
59
+ head, tail = '', ''
60
+ if type.dims.last == 0
61
+ head += encode_type(Type.size_type, arg.size)
62
+ else
63
+ raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last
64
+ end
65
+
66
+ sub_type = type.subtype
67
+ sub_size = type.subtype.size
68
+ arg.size.times do |i|
69
+ if sub_size.nil?
70
+ head += encode_type(Type.size_type, 32*arg.size + tail.size)
71
+ tail += encode_type(sub_type, arg[i])
72
+ else
73
+ head += encode_type(sub_type, arg[i])
74
+ end
75
+ end
76
+
77
+ "#{head}#{tail}"
78
+ else # static type
79
+ if type.dims.empty?
80
+ encode_primitive_type type, arg
81
+ else
82
+ arg.map {|x| encode_type(type.subtype, x) }.join
83
+ end
84
+ end
85
+ end
86
+
87
+ def encode_primitive_type(type, arg)
88
+ case type.base
89
+ when 'uint'
90
+ begin
91
+ real_size = type.sub.to_i
92
+ i = get_uint arg
93
+
94
+ raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
95
+ Utils.zpad_int i
96
+ rescue EncodingError
97
+ raise ValueOutOfBounds, arg
98
+ end
99
+ when 'bool'
100
+ raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
101
+ Utils.zpad_int(arg ? 1 : 0)
102
+ when 'int'
103
+ begin
104
+ real_size = type.sub.to_i
105
+ i = get_int arg
106
+
107
+ raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
108
+ Utils.zpad_int(i % 2**type.sub.to_i)
109
+ rescue EncodingError
110
+ raise ValueOutOfBounds, arg
111
+ end
112
+ when 'ufixed'
113
+ high, low = type.sub.split('x').map(&:to_i)
114
+
115
+ raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**high
116
+ Utils.zpad_int((arg * 2**low).to_i)
117
+ when 'fixed'
118
+ high, low = type.sub.split('x').map(&:to_i)
119
+
120
+ raise ValueOutOfBounds, arg unless arg >= -2**(high - 1) && arg < 2**(high - 1)
121
+
122
+ i = (arg * 2**low).to_i
123
+ Utils.zpad_int(i % 2**(high+low))
124
+ when 'string'
125
+ if arg.encoding.name == 'UTF-8'
126
+ arg = arg.b
127
+ else
128
+ begin
129
+ arg.unpack('U*')
130
+ rescue ArgumentError
131
+ raise ValueError, "string must be UTF-8 encoded"
132
+ end
133
+ end
134
+
135
+ if type.sub.empty? # variable length type
136
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
137
+ size = Utils.zpad_int arg.size
138
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
139
+ "#{size}#{value}"
140
+ else # fixed length type
141
+ sub = type.sub.to_i
142
+ raise ValueOutOfBounds, "invalid string length #{sub}" if arg.size > sub
143
+ raise ValueOutOfBounds, "invalid string length #{sub}" if sub < 0 || sub > 32
144
+ Utils.rpad(arg, BYTE_ZERO, 32)
145
+ end
146
+ when 'bytes'
147
+ raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
148
+ arg = arg.b
149
+
150
+ if type.sub.empty? # variable length type
151
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
152
+ size = Utils.zpad_int arg.size
153
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
154
+ "#{size}#{value}"
155
+ else # fixed length type
156
+ sub = type.sub.to_i
157
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if arg.size > sub
158
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if sub < 0 || sub > 32
159
+ Utils.rpad(arg, BYTE_ZERO, 32)
160
+ end
161
+ when 'hash'
162
+ size = type.sub.to_i
163
+ raise EncodingError, "too long: #{arg}" unless size > 0 && size <= 32
164
+
165
+ if arg.is_a?(Integer)
166
+ Utils.zpad_int(arg)
167
+ elsif arg.size == size
168
+ Utils.zpad arg, 32
169
+ elsif arg.size == size * 2
170
+ Utils.zpad_hex arg
171
+ else
172
+ raise EncodingError, "Could not parse hash: #{arg}"
173
+ end
174
+ when 'address'
175
+ if arg.is_a?(Integer)
176
+ Utils.zpad_int arg
177
+ elsif arg.size == 20
178
+ Utils.zpad arg, 32
179
+ elsif arg.size == 40
180
+ Utils.zpad_hex arg
181
+ elsif arg.size == 42 && arg[0,2] == '0x'
182
+ Utils.zpad_hex arg[2..-1]
183
+ else
184
+ raise EncodingError, "Could not parse address: #{arg}"
185
+ end
186
+ else
187
+ raise EncodingError, "Unhandled type: #{type.base} #{type.sub}"
188
+ end
189
+ end
190
+
191
+
192
+ def min_data_size types
193
+ types.size*32
194
+ end
195
+
196
+ ##
197
+ # Decodes multiple arguments using the head/tail mechanism.
198
+ #
199
+ def decode_abi types, data, raise_errors = false
200
+ parsed_types = types.map {|t| Type.parse(t) }
201
+
202
+ outputs = [nil] * types.size
203
+ start_positions = [nil] * types.size + [data.size]
204
+
205
+ # TODO: refactor, a reverse iteration will be better
206
+ pos = 0
207
+ parsed_types.each_with_index do |t, i|
208
+ # If a type is static, grab the data directly, otherwise record its
209
+ # start position
210
+ if t.dynamic?
211
+
212
+ if raise_errors && pos>data.size-1
213
+ raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
214
+ end
215
+
216
+ start_positions[i] = Utils.big_endian_to_int(data[pos, 32])
217
+
218
+ if raise_errors && start_positions[i]>data.size-1
219
+ raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
220
+ end
221
+
222
+ j = i - 1
223
+ while j >= 0 && start_positions[j].nil?
224
+ start_positions[j] = start_positions[i]
225
+ j -= 1
226
+ end
227
+
228
+ pos += 32
229
+ else
230
+ outputs[i] = zero_padding data, pos, t.size, start_positions
231
+ pos += t.size
232
+ end
233
+ end
234
+
235
+ # We add a start position equal to the length of the entire data for
236
+ # convenience.
237
+ j = types.size - 1
238
+ while j >= 0 && start_positions[j].nil?
239
+ start_positions[j] = start_positions[types.size]
240
+ j -= 1
241
+ end
242
+
243
+ if raise_errors && pos > data.size
244
+ raise DecodingError, "Not enough data for head"
245
+ end
246
+
247
+
248
+ parsed_types.each_with_index do |t, i|
249
+ if t.dynamic?
250
+ offset, next_offset = start_positions[i, 2]
251
+ if offset<=data.size && next_offset<=data.size
252
+ outputs[i] = data[offset...next_offset]
253
+ end
254
+ end
255
+ end
256
+
257
+ if raise_errors && outputs.include?(nil)
258
+ raise DecodingError, "Not all data can be parsed"
259
+ end
260
+
261
+ parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) }
262
+ end
263
+ alias :decode :decode_abi
264
+
265
+ def zero_padding data, pos, count, start_positions
266
+ if pos >= data.size
267
+ start_positions[start_positions.size-1] += count
268
+ "\x00"*count
269
+ elsif pos + count > data.size
270
+ start_positions[start_positions.size-1] += ( count - (data.size-pos))
271
+ data[pos,data.size-pos] + "\x00"*( count - (data.size-pos))
272
+ else
273
+ data[pos, count]
274
+ end
275
+ end
276
+
277
+ def decode_typed_data type_name, data
278
+ decode_primitive_type Type.parse(type_name), data
279
+ end
280
+
281
+ def decode_type(type, arg)
282
+ return nil if arg.nil? || arg.empty?
283
+ if type.kind_of?(Tuple) && type.dims.empty?
284
+ arg ? decode_abi(type.types, arg) : []
285
+ elsif %w(string bytes).include?(type.base) && type.sub.empty?
286
+ l = Utils.big_endian_to_int arg[0,32]
287
+ data = arg[32..-1]
288
+ data[0, l]
289
+ elsif !type.dims.empty? && (l = type.dims.last)>0 # static-sized arrays
290
+ subtype = type.subtype
291
+ if subtype.dynamic?
292
+ start_positions = (0...l).map {|i| Utils.big_endian_to_int(arg[32*i, 32]) }
293
+ start_positions.push arg.size
294
+
295
+ outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
296
+
297
+ outputs.map {|out| decode_type(subtype, out) }
298
+ else
299
+ (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
300
+ end
301
+
302
+ elsif type.dynamic?
303
+ l = Utils.big_endian_to_int arg[0,32]
304
+ raise DecodingError, "Too long length: #{l}" if l>100000
305
+ subtype = type.subtype
306
+
307
+ if subtype.dynamic?
308
+ raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
309
+
310
+ start_positions = (1..l).map {|i| 32+Utils.big_endian_to_int(arg[32*i, 32]) }
311
+ start_positions.push arg.size
312
+
313
+ outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
314
+
315
+ outputs.map {|out| decode_type(subtype, out) }
316
+ else
317
+ (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
318
+ end
319
+
320
+ else
321
+ decode_primitive_type type, arg
322
+ end
323
+ end
324
+
325
+ def decode_primitive_type(type, data)
326
+ case type.base
327
+ when 'address'
328
+ Utils.encode_hex data[12..-1]
329
+ when 'string', 'bytes'
330
+ if type.sub.empty? # dynamic
331
+ if data.length==32
332
+ data[0..32]
333
+ else
334
+ size = Utils.big_endian_to_int data[0,32]
335
+ data[32..-1][0,size]
336
+ end
337
+ else # fixed
338
+ data[0, type.sub.to_i]
339
+ end
340
+ when 'hash'
341
+ data[(32 - type.sub.to_i), type.sub.to_i]
342
+ when 'uint'
343
+ Utils.big_endian_to_int data
344
+ when 'int'
345
+ u = Utils.big_endian_to_int data
346
+ u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
347
+ when 'ufixed'
348
+ high, low = type.sub.split('x').map(&:to_i)
349
+ Utils.big_endian_to_int(data) * 1.0 / 2**low
350
+ when 'fixed'
351
+ high, low = type.sub.split('x').map(&:to_i)
352
+ u = Utils.big_endian_to_int data
353
+ i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
354
+ i * 1.0 / 2**low
355
+ when 'bool'
356
+ data[-1] == BYTE_ONE
357
+ else
358
+ raise DecodingError, "Unknown primitive type: #{type.base}"
359
+ end
360
+ end
361
+
362
+ private
363
+
364
+ def get_uint(n)
365
+ case n
366
+ when Integer
367
+ raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
368
+ n
369
+ when String
370
+ i = if n.size == 40
371
+ Utils.decode_hex(n)
372
+ elsif n.size <= 32
373
+ n
374
+ else
375
+ raise EncodingError, "String too long: #{n}"
376
+ end
377
+ i = Utils.big_endian_to_int i
378
+
379
+ raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
380
+ i
381
+ when true
382
+ 1
383
+ when false, nil
384
+ 0
385
+ else
386
+ raise EncodingError, "Cannot decode uint: #{n}"
387
+ end
388
+ end
389
+
390
+ def get_int(n)
391
+ case n
392
+ when Integer
393
+ raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
394
+ n
395
+ when String
396
+ i = if n.size == 40
397
+ Utils.decode_hex(n)
398
+ elsif n.size <= 32
399
+ n
400
+ else
401
+ raise EncodingError, "String too long: #{n}"
402
+ end
403
+ i = Utils.big_endian_to_int i
404
+
405
+ i = i > INT_MAX ? (i-TT256) : i
406
+ raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
407
+ i
408
+ when true
409
+ 1
410
+ when false, nil
411
+ 0
412
+ else
413
+ raise EncodingError, "Cannot decode int: #{n}"
414
+ end
415
+ end
416
+
417
+ end
418
+
419
+ end # module Abi
420
+ end # module Ethlite
421
+
422
+
423
+
@@ -0,0 +1,32 @@
1
+
2
+ module Ethlite
3
+ module Abi
4
+ module Constant
5
+
6
+ BYTE_EMPTY = "".freeze
7
+ BYTE_ZERO = "\x00".freeze
8
+ BYTE_ONE = "\x01".freeze
9
+
10
+ TT32 = 2**32
11
+ TT40 = 2**40
12
+ TT160 = 2**160
13
+ TT256 = 2**256
14
+ TT64M1 = 2**64 - 1
15
+
16
+ UINT_MAX = 2**256 - 1
17
+ UINT_MIN = 0
18
+ INT_MAX = 2**255 - 1
19
+ INT_MIN = -2**255
20
+
21
+ HASH_ZERO = ("\x00"*32).freeze
22
+
23
+ PUBKEY_ZERO = ("\x00"*32).freeze
24
+ PRIVKEY_ZERO = ("\x00"*32).freeze
25
+ PRIVKEY_ZERO_HEX = ('0'*64).freeze
26
+
27
+ CONTRACT_CODE_SIZE_LIMIT = 0x6000
28
+
29
+ end
30
+
31
+ end # module Abi
32
+ end # module Ethlite
@@ -0,0 +1,29 @@
1
+
2
+ module Ethlite
3
+ module Abi
4
+
5
+ class DeprecatedError < StandardError; end
6
+ class ChecksumError < StandardError; end
7
+ class FormatError < StandardError; end
8
+ class ValidationError < StandardError; end
9
+ class ValueError < StandardError; end
10
+ class AssertError < StandardError; end
11
+
12
+ class UnknownParentError < StandardError; end
13
+ class InvalidBlock < ValidationError; end
14
+ class InvalidUncles < ValidationError; end
15
+
16
+ class InvalidTransaction < ValidationError; end
17
+ class UnsignedTransactionError < InvalidTransaction; end
18
+ class InvalidNonce < InvalidTransaction; end
19
+ class InsufficientStartGas < InvalidTransaction; end
20
+ class InsufficientBalance < InvalidTransaction; end
21
+ class BlockGasLimitReached < InvalidTransaction; end
22
+
23
+ class InvalidSPVProof < ValidationError; end
24
+
25
+ class ContractCreationFailed < StandardError; end
26
+ class TransactionFailed < StandardError; end
27
+
28
+ end # module Abi
29
+ end # module Ethlite
@@ -0,0 +1,203 @@
1
+ module Ethlite
2
+ module Abi
3
+ class Type
4
+
5
+ class ParseError < StandardError;
6
+ end
7
+
8
+ class <<self
9
+ ##
10
+ # Crazy regexp to seperate out base type component (eg. uint), size (eg.
11
+ # 256, 128x128, nil), array component (eg. [], [45], nil)
12
+ #
13
+ def parse(type)
14
+
15
+ return parse('uint256') if type=='trcToken'
16
+
17
+ if type =~ /^\((.*)\)((\[[0-9]*\])*)/
18
+ return Tuple.parse $1, $2.scan(/\[[0-9]*\]/)
19
+ end
20
+
21
+ _, base, sub, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
22
+
23
+ dims = dimension.scan(/\[[0-9]*\]/)
24
+ raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
25
+
26
+ case base
27
+ when ''
28
+ return parse 'address'
29
+ when 'bytes', 'string'
30
+ raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
31
+ when 'uint', 'int'
32
+ raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
33
+
34
+ size = sub.to_i
35
+ raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
36
+ raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
37
+ when 'fixed', 'ufixed'
38
+ raise ParseError, "Fixed type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
39
+
40
+ high, low = sub.split('x').map(&:to_i)
41
+ total = high + low
42
+
43
+ raise ParseError, "Fixed size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
44
+ raise ParseError, "Fixed high size must be multiple of 8" unless high % 8 == 0
45
+ raise ParseError, "Low sizes must be 0 to 80" unless low>0 && low<=80
46
+ when 'hash'
47
+ raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
48
+ when 'address'
49
+ raise ParseError, "Address cannot have suffix" unless sub.empty?
50
+ when 'bool'
51
+ raise ParseError, "Bool cannot have suffix" unless sub.empty?
52
+ else
53
+ raise ParseError, "Unrecognized type base: #{base}"
54
+ end
55
+
56
+ new(base, sub, dims.map {|x| x[1...-1].to_i})
57
+ end
58
+
59
+ def size_type
60
+ @size_type ||= new('uint', 256, [])
61
+ end
62
+ end
63
+
64
+ attr :base, :sub, :dims
65
+
66
+ ##
67
+ # @param base [String] base name of type, e.g. uint for uint256[4]
68
+ # @param sub [String] subscript of type, e.g. 256 for uint256[4]
69
+ # @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
70
+ # for uint256[1][2][], [] for non-array type
71
+ #
72
+ def initialize(base, sub, dims)
73
+ @base = base
74
+ @sub = sub
75
+ @dims = dims
76
+ end
77
+
78
+ def ==(another_type)
79
+ base == another_type.base &&
80
+ sub == another_type.sub &&
81
+ dims == another_type.dims
82
+ end
83
+
84
+ ##
85
+ # Get the static size of a type, or nil if dynamic.
86
+ #
87
+ # @return [Integer, NilClass] size of static type, or nil for dynamic
88
+ # type
89
+ #
90
+ def size
91
+ @size ||= if dims.empty?
92
+ if %w(string bytes).include?(base) && sub.empty?
93
+ nil
94
+ else
95
+ 32
96
+ end
97
+ else
98
+ if dims.last == 0 # 0 for dynamic array []
99
+ nil
100
+ else
101
+ subtype.dynamic? ? nil : dims.last * subtype.size
102
+ end
103
+ end
104
+ end
105
+
106
+ def dynamic?
107
+ size.nil?
108
+ end
109
+
110
+ ##
111
+ # Type with one dimension lesser.
112
+ #
113
+ # @example
114
+ # Type.parse("uint256[2][]").subtype # => Type.new('uint', 256, [2])
115
+ #
116
+ # @return [Ethereum::ABI::Type]
117
+ #
118
+ def subtype
119
+ @subtype ||= self.class.new(base, sub, dims[0...-1])
120
+ end
121
+
122
+ end
123
+
124
+ class Tuple < Type
125
+
126
+ def self.parse types, dims
127
+
128
+ depth = 0
129
+ collected = []
130
+ current = ''
131
+
132
+ types.split('').each do |c|
133
+ case c
134
+ when ',' then
135
+ if depth==0
136
+ collected << current
137
+ current = ''
138
+ else
139
+ current += c
140
+ end
141
+ when '(' then
142
+ depth += 1
143
+ current += c
144
+ when ')' then
145
+ depth -= 1
146
+ current += c
147
+ else
148
+ current += c
149
+ end
150
+
151
+ end
152
+ collected << current unless current.empty?
153
+
154
+ Tuple.new collected, dims.map {|x| x[1...-1].to_i}
155
+
156
+ end
157
+
158
+ attr_reader :types, :parsed_types
159
+ def initialize types, dims
160
+ super('tuple', '', dims)
161
+ @types = types
162
+ @parsed_types = types.map{|t| Type.parse t}
163
+ end
164
+
165
+ def ==(another_type)
166
+ another_type.kind_of?(Tuple) &&
167
+ another_type.types == types &&
168
+ another_type.dims == dims
169
+ end
170
+
171
+ def size
172
+ @size ||= calculate_size
173
+ end
174
+
175
+ def calculate_size
176
+ if dims.empty?
177
+ s = 0
178
+ parsed_types.each do |type|
179
+ ts = type.size
180
+ return nil if ts.nil?
181
+ s += ts
182
+ end
183
+ s
184
+ else
185
+ if dims.last == 0 # 0 for dynamic array []
186
+ nil
187
+ else
188
+ subtype.dynamic? ? nil : dims.last * subtype.size
189
+ end
190
+ end
191
+
192
+
193
+ end
194
+
195
+ def subtype
196
+ @subtype ||= Tuple.new(types, dims[0...-1])
197
+ end
198
+
199
+ end
200
+
201
+
202
+ end # module Abi
203
+ end # module Ethlite
@@ -0,0 +1,223 @@
1
+ module Ethlite
2
+ module Abi
3
+ module Utils
4
+
5
+ extend self
6
+
7
+ include Constant
8
+
9
+ ##
10
+ # Not the keccak in sha3, although it's underlying lib named SHA3
11
+ #
12
+ def keccak256(x)
13
+ # Digest::SHA3.new(256).digest(x)
14
+ Digest::Keccak.digest(x, 256)
15
+ end
16
+
17
+ def keccak512(x)
18
+ # Digest::SHA3.new(512).digest(x)
19
+ Digest::Keccak.digest(x, 512)
20
+ end
21
+
22
+ def keccak256_rlp(x)
23
+ keccak256 RLP.encode(x)
24
+ end
25
+
26
+ def sha256(x)
27
+ Digest::SHA256.digest x
28
+ end
29
+
30
+ def double_sha256(x)
31
+ sha256 sha256(x)
32
+ end
33
+
34
+ def ripemd160(x)
35
+ Digest::RMD160.digest x
36
+ end
37
+
38
+ def hash160(x)
39
+ ripemd160 sha256(x)
40
+ end
41
+
42
+ def hash160_hex(x)
43
+ encode_hex hash160(x)
44
+ end
45
+
46
+ def mod_exp(x, y, n)
47
+ x.to_bn.mod_exp(y, n).to_i
48
+ end
49
+
50
+ def mod_mul(x, y, n)
51
+ x.to_bn.mod_mul(y, n).to_i
52
+ end
53
+
54
+ def to_signed(i)
55
+ i > Constant::INT_MAX ? (i-Constant::TT256) : i
56
+ end
57
+
58
+ def base58_check_to_bytes(s)
59
+ leadingzbytes = s.match(/\A1*/)[0]
60
+ data = Constant::BYTE_ZERO * leadingzbytes.size + BaseConvert.convert(s, 58, 256)
61
+
62
+ raise ChecksumError, "double sha256 checksum doesn't match" unless double_sha256(data[0...-4])[0,4] == data[-4..-1]
63
+ data[1...-4]
64
+ end
65
+
66
+ def bytes_to_base58_check(bytes, magicbyte=0)
67
+ bs = "#{magicbyte.chr}#{bytes}"
68
+ leadingzbytes = bs.match(/\A#{Constant::BYTE_ZERO}*/)[0]
69
+ checksum = double_sha256(bs)[0,4]
70
+ '1'*leadingzbytes.size + BaseConvert.convert("#{bs}#{checksum}", 256, 58)
71
+ end
72
+
73
+ def ceil32(x)
74
+ x % 32 == 0 ? x : (x + 32 - x%32)
75
+ end
76
+
77
+ def encode_hex(b)
78
+ RLP::Utils.encode_hex b
79
+ end
80
+
81
+ def decode_hex(s)
82
+ RLP::Utils.decode_hex s
83
+ end
84
+
85
+ def big_endian_to_int(s)
86
+ RLP::Sedes.big_endian_int.deserialize s.sub(/\A(\x00)+/, '')
87
+ end
88
+
89
+ def int_to_big_endian(n)
90
+ RLP::Sedes.big_endian_int.serialize n
91
+ end
92
+
93
+ def lpad(x, symbol, l)
94
+ return x if x.size >= l
95
+ symbol * (l - x.size) + x
96
+ end
97
+
98
+ def rpad(x, symbol, l)
99
+ return x if x.size >= l
100
+ x + symbol * (l - x.size)
101
+ end
102
+
103
+ def zpad(x, l)
104
+ lpad x, BYTE_ZERO, l
105
+ end
106
+
107
+ def zunpad(x)
108
+ x.sub /\A\x00+/, ''
109
+ end
110
+
111
+ def zpad_int(n, l=32)
112
+ zpad encode_int(n), l
113
+ end
114
+
115
+ def zpad_hex(s, l=32)
116
+ zpad decode_hex(s), l
117
+ end
118
+
119
+ def int_to_addr(x)
120
+ zpad_int x, 20
121
+ end
122
+
123
+ def encode_int(n)
124
+ raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
125
+ int_to_big_endian n
126
+ end
127
+
128
+ def decode_int(v)
129
+ raise ArgumentError, "No leading zero bytes allowed for integers" if v.size > 0 && (v[0] == Constant::BYTE_ZERO || v[0] == 0)
130
+ big_endian_to_int v
131
+ end
132
+
133
+ def bytearray_to_int(arr)
134
+ o = 0
135
+ arr.each {|x| o = (o << 8) + x }
136
+ o
137
+ end
138
+
139
+ def int_array_to_bytes(arr)
140
+ arr.pack('C*')
141
+ end
142
+
143
+ def bytes_to_int_array(bytes)
144
+ bytes.unpack('C*')
145
+ end
146
+
147
+ def coerce_to_int(x)
148
+ if x.is_a?(Numeric)
149
+ x
150
+ elsif x.size == 40
151
+ big_endian_to_int decode_hex(x)
152
+ else
153
+ big_endian_to_int x
154
+ end
155
+ end
156
+
157
+ def coerce_to_bytes(x)
158
+ if x.is_a?(Numeric)
159
+ int_to_big_endian x
160
+ elsif x.size == 40
161
+ decode_hex(x)
162
+ else
163
+ x
164
+ end
165
+ end
166
+
167
+ def coerce_addr_to_hex(x)
168
+ if x.is_a?(Numeric)
169
+ encode_hex zpad(int_to_big_endian(x), 20)
170
+ elsif x.size == 40 || x.size == 0
171
+ x
172
+ else
173
+ encode_hex zpad(x, 20)[-20..-1]
174
+ end
175
+ end
176
+
177
+ def normalize_address(x, allow_blank: false)
178
+ address = Address.new(x)
179
+ raise ValueError, "address is blank" if !allow_blank && address.blank?
180
+ address.to_bytes
181
+ end
182
+
183
+ def mk_contract_address(sender, nonce)
184
+ keccak256_rlp([normalize_address(sender), nonce])[12..-1]
185
+ end
186
+
187
+ def mk_metropolis_contract_address(sender, initcode)
188
+ keccak256(normalize_address(sender) + initcode)[12..-1]
189
+ end
190
+
191
+
192
+ def parse_int_or_hex(s)
193
+ if s.is_a?(Numeric)
194
+ s
195
+ elsif s[0,2] == '0x'
196
+ big_endian_to_int decode_hex(normalize_hex_without_prefix(s))
197
+ else
198
+ s.to_i
199
+ end
200
+ end
201
+
202
+ def normalize_hex_without_prefix(s)
203
+ if s[0,2] == '0x'
204
+ (s.size % 2 == 1 ? '0' : '') + s[2..-1]
205
+ else
206
+ s
207
+ end
208
+ end
209
+
210
+ def function_signature method_name, arg_types
211
+ "#{method_name}(#{arg_types.join(',')})"
212
+ end
213
+
214
+ def signature_hash signature, length=64
215
+ encode_hex(keccak256(signature))[0...length]
216
+ end
217
+
218
+
219
+ end
220
+
221
+ end # module Abi
222
+ end # module Ethlite
223
+
@@ -0,0 +1,64 @@
1
+ module Ethlite
2
+ class ContractMethod
3
+ include Utility
4
+
5
+ attr_reader :abi,
6
+ :signature,
7
+ :name,
8
+ :signature_hash,
9
+ :input_types,
10
+ :output_types,
11
+ :constant
12
+
13
+ def initialize( abi )
14
+ ## convenience helper - auto-convert to json if string passed in
15
+ abi = JSON.parse( abi ) if abi.is_a?( String )
16
+
17
+ @abi = abi
18
+ @name = abi['name']
19
+ @constant = !!abi['constant'] || abi['stateMutability']=='view'
20
+ @input_types = abi['inputs'] ? abi['inputs'].map{|a| parse_component_type a } : []
21
+ @output_types = abi['outputs'] ? abi['outputs'].map{|a| parse_component_type a } : nil
22
+ @signature = Abi::Utils.function_signature( @name, @input_types )
23
+ @signature_hash = Abi::Utils.signature_hash( @signature, abi['type']=='event' ? 64 : 8)
24
+ end
25
+
26
+ def parse_component_type( argument )
27
+ if argument['type'] =~ /^tuple((\[[0-9]*\])*)/
28
+ argument['components'] ? "(#{argument['components'].collect{|c| parse_component_type( c ) }.join(',')})#{$1}"
29
+ : "()#{$1}"
30
+ else
31
+ argument['type']
32
+ end
33
+ end
34
+
35
+
36
+ def do_call( rpc, contract_address, args )
37
+ data = '0x' + @signature_hash + Abi::Utils.encode_hex(
38
+ Abi::AbiCoder.encode_abi(@input_types, args) )
39
+
40
+ method = 'eth_call'
41
+ params = [{ to: contract_address,
42
+ data: data},
43
+ 'latest']
44
+ response = rpc.request( method, params )
45
+
46
+
47
+ puts "response:"
48
+ pp response
49
+
50
+ string_data = [remove_0x_head(response)].pack('H*')
51
+ return nil if string_data.empty?
52
+
53
+ result = Abi::AbiCoder.decode_abi( @output_types, string_data )
54
+ puts
55
+ puts "result decode_abi:"
56
+ pp result
57
+
58
+
59
+ result.length==1 ? result.first : result
60
+ end
61
+
62
+ end # class ContractMethod
63
+ end # module Ethlite
64
+
@@ -0,0 +1,48 @@
1
+ module Ethlite
2
+
3
+ class Rpc
4
+ def initialize( uri )
5
+ @client_id = Random.rand( 10000000 )
6
+
7
+ @uri = URI.parse( uri )
8
+ end
9
+
10
+ def request( method, params=[] )
11
+ opts = {}
12
+ if @uri.instance_of?( URI::HTTPS )
13
+ opts[:use_ssl] = true
14
+ opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
15
+ end
16
+
17
+ Net::HTTP.start( @uri.host, @uri.port, **opts ) do |http|
18
+ headers = {"Content-Type" => "application/json"}
19
+ request = Net::HTTP::Post.new( @uri.request_uri, headers )
20
+
21
+ json = { jsonrpc: '2.0',
22
+ method: method,
23
+ params: params,
24
+ id: @client_id }.to_json
25
+
26
+ puts "json POST payload:"
27
+ puts json
28
+
29
+ request.body = json
30
+ response = http.request( request )
31
+
32
+ raise "Error code #{response.code} on request #{@uri.to_s} #{request.body}" unless response.kind_of?( Net::HTTPOK )
33
+
34
+ body = JSON.parse(response.body, max_nesting: 1500)
35
+
36
+ if body['result']
37
+ body['result']
38
+ elsif body['error']
39
+ raise "Error #{@uri.to_s} #{body['error']} on request #{@uri.to_s} #{request.body}"
40
+ else
41
+ raise "No response on request #{@uri.to_s} #{request.body}"
42
+ end
43
+ end
44
+ end
45
+ end # class Rpc
46
+
47
+ end # module Ethlite
48
+
@@ -0,0 +1,23 @@
1
+ module Ethlite
2
+
3
+ module Utility
4
+
5
+ def hex( num )
6
+ '0x' + num.to_s(16)
7
+ end
8
+
9
+ def from_hex( h )
10
+ h.nil? ? 0 : (h.kind_of?(String) ? h.to_i(16) : h)
11
+ end
12
+
13
+ def remove_0x_head( s )
14
+ return s if !s || s.length<2
15
+ s[0,2] == '0x' ? s[2..-1] : s
16
+ end
17
+
18
+
19
+ def wei_to_ether( wei )
20
+ 1.0 * wei / 10**18
21
+ end
22
+ end
23
+ end # module Ethlite
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Ethlite
4
4
  MAJOR = 0
5
- MINOR = 1
5
+ MINOR = 2
6
6
  PATCH = 0
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
data/lib/ethlite.rb CHANGED
@@ -1,9 +1,37 @@
1
1
  require 'cocos'
2
2
 
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'json'
7
+
8
+
9
+ require 'openssl'
10
+ require 'digest'
11
+
12
+ ## 3rd party gems
13
+ require 'digest/keccak' ## gem keccak - see https://rubygems.org/gems/keccak
14
+ require 'rlp' ## gem rlp - see https://rubygems.org/gems/rlp
15
+
16
+
17
+
3
18
  ## our own code
4
19
  require_relative 'ethlite/version' # note: let version always go first
5
20
 
6
21
 
22
+ require_relative 'ethlite/abi/type'
23
+ require_relative 'ethlite/abi/constant'
24
+ require_relative 'ethlite/abi/exceptions'
25
+ require_relative 'ethlite/abi/utils'
26
+ require_relative 'ethlite/abi/abi_coder'
27
+
28
+
29
+ require_relative 'ethlite/rpc'
30
+
31
+ require_relative 'ethlite/utility'
32
+ require_relative 'ethlite/contract'
33
+
34
+
7
35
 
8
36
  ## add convenience alternate spelling
9
37
  EthLite = Ethlite
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethlite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: keccak
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rlp
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rdoc
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +101,14 @@ files:
73
101
  - README.md
74
102
  - Rakefile
75
103
  - lib/ethlite.rb
104
+ - lib/ethlite/abi/abi_coder.rb
105
+ - lib/ethlite/abi/constant.rb
106
+ - lib/ethlite/abi/exceptions.rb
107
+ - lib/ethlite/abi/type.rb
108
+ - lib/ethlite/abi/utils.rb
109
+ - lib/ethlite/contract.rb
110
+ - lib/ethlite/rpc.rb
111
+ - lib/ethlite/utility.rb
76
112
  - lib/ethlite/version.rb
77
113
  homepage: https://github.com/pixelartexchange/artbase
78
114
  licenses: