ethlite 0.1.0 → 0.2.0

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