ruby-ethereum 0.9.3 → 0.9.4

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
  SHA1:
3
- metadata.gz: 8c18a40778f44a8f514e98f791b5a5238fc5e5f8
4
- data.tar.gz: 0589c28717d274a533bb50612b6dd0fc14e99e0a
3
+ metadata.gz: d5dea8f0df777458359a58003ad2c36d5bab5c97
4
+ data.tar.gz: 3ad437895256c6a02baf5a65502e87f6fc2d3b3d
5
5
  SHA512:
6
- metadata.gz: 889d80e5709de13b79b5877003c943f8d8d114fec8d42788f8835b8c935920535eee15c0be0d899853fba6d5c0075530aa3269bc745f5410f7a238ba084723ad
7
- data.tar.gz: 9a1b4ba43049ae72d983cf9ba48d551dd21a54c01d5bc481828360b0b4abf6ff2dcdb1f7175673d9805cc8a4e3e30156e603be9a3aef27b31e181b5e95c8d84c
6
+ metadata.gz: f85feb3ff2fadd5822eb1927d714f3afa6c9bdab8fa4a475e1e34cbdad8b3e901a907395ad69fbe3f284ee23bc0b7f01c17729b684c379f2b7f57aeb26ae9814
7
+ data.tar.gz: 63eeecac33f4f8a60cc1274097d8871cc708debff8e6e304279485e34a22b1fd8bce2bb64f4db4023e59d4e56d91440c66a7a9faa6f49e5911f138e9c4ad4430
data/README.md CHANGED
@@ -6,15 +6,7 @@ A Ruby implementation of [Ethereum](https://ethereum.org).
6
6
 
7
7
  ## Install Secp256k1
8
8
 
9
- ```
10
- git clone git@github.com:bitcoin/bitcoin.git
11
- git checkout v0.11.2
12
-
13
- ./autogen.sh
14
- ./configure
15
- make
16
- sudo make install
17
- ```
9
+ https://github.com/cryptape/ruby-bitcoin-secp256k1
18
10
 
19
11
  ## Caveats
20
12
 
data/lib/ethereum/abi.rb CHANGED
@@ -18,7 +18,7 @@ module Ethereum
18
18
 
19
19
  class EncodingError < StandardError; end
20
20
  class DecodingError < StandardError; end
21
- class ValueOutOfBounds < StandardError; end
21
+ class ValueOutOfBounds < ValueError; end
22
22
 
23
23
  ##
24
24
  # Encodes multiple arguments using the head/tail mechanism.
@@ -54,12 +54,7 @@ module Ethereum
54
54
  #
55
55
  def encode_type(type, arg)
56
56
  if %w(string bytes).include?(type.base) && type.sub.empty?
57
- raise ArgumentError, "arg must be a string" unless arg.instance_of?(String)
58
-
59
- size = encode_type Type.size_type, arg.size
60
- padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size)
61
-
62
- "#{size}#{arg}#{padding}"
57
+ encode_primitive_type type, arg
63
58
  elsif type.dynamic?
64
59
  raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
65
60
 
@@ -94,44 +89,76 @@ module Ethereum
94
89
  def encode_primitive_type(type, arg)
95
90
  case type.base
96
91
  when 'uint'
97
- real_size = type.sub.to_i
98
- i = get_uint arg
99
-
100
- raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
101
- Utils.zpad_int i
92
+ begin
93
+ real_size = type.sub.to_i
94
+ i = get_uint arg
95
+
96
+ raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
97
+ Utils.zpad_int i
98
+ rescue EncodingError
99
+ raise ValueOutOfBounds, arg
100
+ end
102
101
  when 'bool'
103
102
  raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
104
- Utils.zpad_int(arg ? 1: 0)
103
+ Utils.zpad_int(arg ? 1 : 0)
105
104
  when 'int'
106
- real_size = type.sub.to_i
107
- i = get_int arg
108
-
109
- raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
110
- Utils.zpad_int(i % 2**type.sub.to_i)
111
- when 'ureal', 'ufixed'
105
+ begin
106
+ real_size = type.sub.to_i
107
+ i = get_int arg
108
+
109
+ raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
110
+ Utils.zpad_int(i % 2**type.sub.to_i)
111
+ rescue EncodingError
112
+ raise ValueOutOfBounds, arg
113
+ end
114
+ when 'ufixed'
112
115
  high, low = type.sub.split('x').map(&:to_i)
113
116
 
114
117
  raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**high
115
118
  Utils.zpad_int((arg * 2**low).to_i)
116
- when 'real', 'fixed'
119
+ when 'fixed'
117
120
  high, low = type.sub.split('x').map(&:to_i)
118
121
 
119
122
  raise ValueOutOfBounds, arg unless arg >= -2**(high - 1) && arg < 2**(high - 1)
120
123
 
121
124
  i = (arg * 2**low).to_i
122
125
  Utils.zpad_int(i % 2**(high+low))
123
- when 'string', 'bytes'
124
- raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
126
+ when 'string'
127
+ if arg.encoding.name == 'UTF-8'
128
+ arg = arg.b
129
+ else
130
+ begin
131
+ arg.unpack('U*')
132
+ rescue ArgumentError
133
+ raise ValueError, "string must be UTF-8 encoded"
134
+ end
135
+ end
125
136
 
126
137
  if type.sub.empty? # variable length type
138
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
127
139
  size = Utils.zpad_int arg.size
128
- padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size)
129
- "#{size}#{arg}#{padding}"
140
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
141
+ "#{size}#{value}"
130
142
  else # fixed length type
131
- raise ValueOutOfBounds, arg unless arg.size <= type.sub.to_i
143
+ sub = type.sub.to_i
144
+ raise ValueOutOfBounds, "invalid string length #{sub}" if arg.size > sub
145
+ raise ValueOutOfBounds, "invalid string length #{sub}" if sub < 0 || sub > 32
146
+ Utils.rpad(arg, BYTE_ZERO, 32)
147
+ end
148
+ when 'bytes'
149
+ raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
150
+ arg = arg.b
132
151
 
133
- padding = BYTE_ZERO * (32 - arg.size)
134
- "#{arg}#{padding}"
152
+ if type.sub.empty? # variable length type
153
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
154
+ size = Utils.zpad_int arg.size
155
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
156
+ "#{size}#{value}"
157
+ else # fixed length type
158
+ sub = type.sub.to_i
159
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if arg.size > sub
160
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if sub < 0 || sub > 32
161
+ Utils.rpad(arg, BYTE_ZERO, 32)
135
162
  end
136
163
  when 'hash'
137
164
  size = type.sub.to_i
@@ -266,10 +293,10 @@ module Ethereum
266
293
  when 'int'
267
294
  u = Utils.big_endian_to_int data
268
295
  u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
269
- when 'ureal', 'ufixed'
296
+ when 'ufixed'
270
297
  high, low = type.sub.split('x').map(&:to_i)
271
298
  Utils.big_endian_to_int(data) * 1.0 / 2**low
272
- when 'real', 'fixed'
299
+ when 'fixed'
273
300
  high, low = type.sub.split('x').map(&:to_i)
274
301
  u = Utils.big_endian_to_int data
275
302
  i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
@@ -289,13 +316,17 @@ module Ethereum
289
316
  raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
290
317
  n
291
318
  when String
292
- if n.size == 40
293
- Utils.big_endian_to_int Utils.decode_hex(n)
294
- elsif n.size <= 32
295
- Utils.big_endian_to_int n
296
- else
297
- raise EncodingError, "String too long: #{n}"
298
- end
319
+ i = if n.size == 40
320
+ Utils.decode_hex(n)
321
+ elsif n.size <= 32
322
+ n
323
+ else
324
+ raise EncodingError, "String too long: #{n}"
325
+ end
326
+ i = Utils.big_endian_to_int i
327
+
328
+ raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
329
+ i
299
330
  when true
300
331
  1
301
332
  when false, nil
@@ -311,14 +342,18 @@ module Ethereum
311
342
  raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
312
343
  n
313
344
  when String
314
- if n.size == 40
315
- i = Utils.big_endian_to_int Utils.decode_hex(n)
316
- elsif n.size <= 32
317
- i = Utils.big_endian_to_int n
318
- else
319
- raise EncodingError, "String too long: #{n}"
320
- end
321
- i > INT_MAX ? (i-TT256) : i
345
+ i = if n.size == 40
346
+ Utils.decode_hex(n)
347
+ elsif n.size <= 32
348
+ n
349
+ else
350
+ raise EncodingError, "String too long: #{n}"
351
+ end
352
+ i = Utils.big_endian_to_int i
353
+
354
+ i = i > INT_MAX ? (i-TT256) : i
355
+ raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
356
+ i
322
357
  when true
323
358
  1
324
359
  when false, nil
@@ -6,140 +6,180 @@ module Ethereum
6
6
  module ABI
7
7
  class ContractTranslator
8
8
 
9
- def initialize(full_signature)
10
- @v = {
9
+ def initialize(contract_interface)
10
+ if contract_interface.instance_of?(String)
11
+ contract_interface = JSON.parse contract_interface
12
+ end
13
+
14
+ @contract = {
15
+ constructor_data: nil,
11
16
  function_data: {},
12
17
  event_data: {}
13
18
  }
14
19
 
15
- if full_signature.instance_of?(String)
16
- full_signature = JSON.parse full_signature
17
- end
18
-
19
- full_signature.each do |sig_item|
20
- next if sig_item['type'] == 'constructor'
21
-
22
- encode_types = sig_item['inputs'].map {|f| f['type'] }
23
- signature = sig_item['inputs'].map {|f| [f['type'], f['name']] }
24
- name = sig_item['name']
25
-
26
- if name =~ /\(/
27
- name = name[0, name.index('(')]
28
- end
29
-
30
- # TODO: removable?
31
- #if @v.has_key?(name.to_sym)
32
- # i = 2
33
- # i += 1 while @v.has_key?(:"#{name}#{i}")
34
- # name += i.to_s
35
-
36
- # logger.warn "multiple methods with the same name. Use #{name} to call #{sig_item['name']} with types #{encode_types}"
37
- #end
38
-
39
- if sig_item['type'] == 'function'
40
- decode_types = sig_item['outputs'].map {|f| f['type'] }
41
- is_unknown_type = sig_item['outputs'].size.true? && sig_item['outputs'][0]['name'] == 'unknown_out'
42
- function_data[name.to_sym] = {
20
+ contract_interface.each do |desc|
21
+ encode_types = desc['inputs'].map {|e| e['type'] }
22
+ signature = desc['inputs'].map {|e| [e['type'], e['name']] }
23
+
24
+ # type can be omitted, defaulting to function
25
+ type = desc['type'] || 'function'
26
+ case type
27
+ when 'function'
28
+ name = basename desc['name']
29
+ decode_types = desc['outputs'].map {|e| e['type'] }
30
+ @contract[:function_data][name] = {
43
31
  prefix: method_id(name, encode_types),
44
32
  encode_types: encode_types,
45
33
  decode_types: decode_types,
46
- is_unknown_type: is_unknown_type,
47
- is_constant: sig_item.fetch('constant', false),
34
+ is_constant: desc.fetch('constant', false),
48
35
  signature: signature
49
36
  }
50
- elsif sig_item['type'] == 'event'
51
- indexed = sig_item['inputs'].map {|f| f['indexed'] }
52
- names = sig_item['inputs'].map {|f| f['name'] }
53
-
54
- event_data[event_id(name, encode_types)] = {
37
+ when 'event'
38
+ name = basename desc['name']
39
+ indexed = desc['inputs'].map {|e| e['indexed'] }
40
+ names = desc['inputs'].map {|e| e['name'] }
41
+ @contract[:event_data][event_id(name, encode_types)] = {
55
42
  types: encode_types,
56
43
  name: name,
57
44
  names: names,
58
45
  indexed: indexed,
59
- anonymous: sig_item.fetch('anonymous', false)
46
+ anonymous: desc.fetch('anonymous', false)
60
47
  }
48
+ when 'constructor'
49
+ raise ValueError, "Only one constructor is supported." if @contract[:constructor_data]
50
+ @contract[:constructor_data] = {
51
+ encode_types: encode_types,
52
+ signature: signature
53
+ }
54
+ else
55
+ raise ValueError, "Unknown interface type: #{type}"
61
56
  end
62
57
  end
63
58
  end
64
59
 
60
+ ##
61
+ # Return the encoded function call.
62
+ #
63
+ # @param name [String] One of the existing functions described in the
64
+ # contract interface.
65
+ # @param args [Array[Object]] The function arguments that will be encoded
66
+ # and used in the contract execution in the vm.
67
+ #
68
+ # @return [String] The encoded function name and arguments so that it can
69
+ # be used with the evm to execute a function call, the binary string
70
+ # follows the Ethereum Contract ABI.
71
+ #
65
72
  def encode(name, args)
66
- fdata = function_data[name.to_sym]
67
- id = Utils.zpad(Utils.encode_int(fdata[:prefix]), 4)
68
- calldata = ABI.encode_abi fdata[:encode_types], args
69
- "#{id}#{calldata}"
73
+ raise ValueError, "Unknown function #{name}" unless function_data.include?(name)
74
+
75
+ desc = function_data[name]
76
+ func_id = Utils.zpad(Utils.encode_int(desc[:prefix]), 4)
77
+ calldata = ABI.encode_abi desc[:encode_types], args
78
+
79
+ "#{func_id}#{calldata}"
70
80
  end
71
81
 
72
- def decode(name, data)
73
- fdata = function_data[name.to_sym]
82
+ ##
83
+ # Return the encoded constructor call.
84
+ #
85
+ def encode_constructor_arguments(args)
86
+ raise ValueError, "The contract interface didn't have a constructor" unless constructor_data
74
87
 
75
- if fdata[:is_unknown_type]
76
- i = 0
77
- o = []
88
+ ABI.encode_abi constructor_data[:encode_types], args
89
+ end
78
90
 
79
- while i < data.size
80
- o.push Utils.to_signed(Utils.big_endian_to_int(data[i,32]))
81
- i += 32
82
- end
91
+ ##
92
+ # Return the function call result decoded.
93
+ #
94
+ # @param name [String] One of the existing functions described in the
95
+ # contract interface.
96
+ # @param data [String] The encoded result from calling function `name`.
97
+ #
98
+ # @return [Array[Object]] The values returned by the call to function
99
+ #
100
+ def decode_function_result(name, data)
101
+ desc = function_data[name]
102
+ ABI.decode_abi desc[:decode_types], data
103
+ end
104
+ alias :decode :decode_function_result
105
+
106
+ ##
107
+ # Return a dictionary represent the log.
108
+ #
109
+ # Notes: this function won't work with anonymous events.
110
+ #
111
+ # @param log_topics [Array[String]] The log's indexed arguments.
112
+ # @param log_data [String] The encoded non-indexed arguments.
113
+ #
114
+ def decode_event(log_topics, log_data)
115
+ # topics[0]: keccak256(normalized_event_name)
116
+ raise ValueError, "Unknown log type" unless log_topics.size > 0 && event_data.has_key?(log_topics[0])
117
+
118
+ event_id = log_topics[0]
119
+ event = event_data[event_id]
120
+
121
+ names = event[:names]
122
+ types = event[:types]
123
+ indexed = event[:indexed]
83
124
 
84
- return 0 if o.empty?
85
- o.size == 1 ? o[0] : o
86
- else
87
- ABI.decode_abi fdata[:decode_types], data
125
+ unindexed_types = types.zip(indexed).select {|(t, i)| i.false? }.map(&:first)
126
+ unindexed_args = ABI.decode_abi unindexed_types, log_data
127
+
128
+ result = {}
129
+ indexed_count = 1 # skip topics[0]
130
+ names.each_with_index do |name, i|
131
+ v = if indexed[i].true?
132
+ topic_bytes = Utils.zpad_int log_topics[indexed_count]
133
+ indexed_count += 1
134
+ ABI.decode_primitive_type ABI::Type.parse(types[i]), topic_bytes
135
+ else
136
+ unindexed_args.shift
137
+ end
138
+
139
+ result[name] = v
88
140
  end
141
+
142
+ result['_event_type'] = event[:name]
143
+ result
144
+ end
145
+
146
+ ##.
147
+ # Return a dictionary representation of the Log instance.
148
+ #
149
+ # Note: this function won't work with anonymous events.
150
+ #
151
+ # @param log [Log] The Log instance that needs to be parsed.
152
+ # @param noprint [Bool] Flag to turn off printing of the decoded log
153
+ # instance.
154
+ #
155
+ def listen(log, noprint: true)
156
+ result = decode_event log.topics, log.data
157
+ p result if noprint
158
+ result
159
+ rescue ValueError
160
+ nil # api compatibility
161
+ end
162
+
163
+ def constructor_data
164
+ @contract[:constructor_data]
89
165
  end
90
166
 
91
167
  def function_data
92
- @v[:function_data]
168
+ @contract[:function_data]
93
169
  end
94
170
 
95
171
  def event_data
96
- @v[:event_data]
172
+ @contract[:event_data]
97
173
  end
98
174
 
99
175
  def function(name)
100
- function_data[name.to_sym]
176
+ function_data[name]
101
177
  end
102
178
 
103
179
  def event(name, encode_types)
104
180
  event_data[event_id(name, encode_types)]
105
181
  end
106
182
 
107
- def is_unknown_type(name)
108
- function_data[name.to_sym][:is_unknown_type]
109
- end
110
-
111
- def listen(log, noprint=false)
112
- return if log.topics.size == 0 || !event_data.has_key?(log.topics[0])
113
-
114
- data = event_data[log.topics[0]]
115
- types = data[:types]
116
- name = data[:name]
117
- names = data[:names]
118
- indexed = data[:indexed]
119
- indexed_types = types.zip(indexed).select {|(t, i)| i.true? }.map(&:first)
120
- unindexed_types = types.zip(indexed).select {|(t, i)| i.false? }.map(&:first)
121
-
122
- deserialized_args = ABI.decode_abi unindexed_types, log.data
123
-
124
- o = {}
125
- c1, c2 = 0, 0
126
- names.each_with_index do |n, i|
127
- if indexed[i].true?
128
- topic_bytes = Utils.zpad_int log.topics[c1+1]
129
- o[n] = ABI.decode_primitive_type ABI::Type.parse(indexed_types[c1]), topic_bytes
130
- c1 += 1
131
- else
132
- o[n] = deserialized_args[c2]
133
- c2 += 1
134
- end
135
- end
136
-
137
- o['_event_type'] = name
138
- p o unless noprint
139
-
140
- o
141
- end
142
-
143
183
  def method_id(name, encode_types)
144
184
  Utils.big_endian_to_int Utils.keccak256(get_sig(name, encode_types))[0,4]
145
185
  end
@@ -155,20 +195,25 @@ module Ethereum
155
195
  end
156
196
 
157
197
  def get_sig(name, encode_types)
158
- "#{name}(#{encode_types.map {|x| canonical_name(x) }.join(',')})"
198
+ "#{name}(#{encode_types.map {|x| canonical_type(x) }.join(',')})"
159
199
  end
160
200
 
161
- def canonical_name(x)
201
+ def canonical_type(x)
162
202
  case x
163
203
  when /\A(uint|int)(\[.*\])?\z/
164
204
  "#{$1}256#{$2}"
165
- when /\A(real|ureal|fixed|ufixed)(\[.*\])?\z/
205
+ when /\A(fixed|ufixed)(\[.*\])?\z/
166
206
  "#{$1}128x128#{$2}"
167
207
  else
168
208
  x
169
209
  end
170
210
  end
171
211
 
212
+ def basename(n)
213
+ i = n.index '('
214
+ i ? n[0,i] : n
215
+ end
216
+
172
217
  end
173
218
  end
174
219
  end