ruby-ethereum 0.9.3 → 0.9.4

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
  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