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 +4 -4
- data/README.md +1 -9
- data/lib/ethereum/abi.rb +79 -44
- data/lib/ethereum/abi/contract_translator.rb +142 -97
- data/lib/ethereum/abi/type.rb +4 -4
- data/lib/ethereum/address.rb +2 -0
- data/lib/ethereum/block.rb +38 -3
- data/lib/ethereum/constant.rb +8 -6
- data/lib/ethereum/env.rb +15 -1
- data/lib/ethereum/external_call.rb +43 -20
- data/lib/ethereum/tester/abi_contract.rb +12 -15
- data/lib/ethereum/tester/solidity_wrapper.rb +249 -81
- data/lib/ethereum/tester/state.rb +50 -36
- data/lib/ethereum/utils.rb +23 -0
- data/lib/ethereum/version.rb +1 -1
- data/lib/ethereum/vm.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5dea8f0df777458359a58003ad2c36d5bab5c97
|
4
|
+
data.tar.gz: 3ad437895256c6a02baf5a65502e87f6fc2d3b3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 <
|
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
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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 '
|
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'
|
124
|
-
|
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
|
-
|
129
|
-
"#{size}#{
|
140
|
+
value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
|
141
|
+
"#{size}#{value}"
|
130
142
|
else # fixed length type
|
131
|
-
|
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
|
-
|
134
|
-
"#{arg}
|
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 '
|
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 '
|
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
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
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(
|
10
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
47
|
-
is_constant: sig_item.fetch('constant', false),
|
34
|
+
is_constant: desc.fetch('constant', false),
|
48
35
|
signature: signature
|
49
36
|
}
|
50
|
-
|
51
|
-
|
52
|
-
|
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:
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
o = []
|
88
|
+
ABI.encode_abi constructor_data[:encode_types], args
|
89
|
+
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
@
|
168
|
+
@contract[:function_data]
|
93
169
|
end
|
94
170
|
|
95
171
|
def event_data
|
96
|
-
@
|
172
|
+
@contract[:event_data]
|
97
173
|
end
|
98
174
|
|
99
175
|
def function(name)
|
100
|
-
function_data[name
|
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|
|
198
|
+
"#{name}(#{encode_types.map {|x| canonical_type(x) }.join(',')})"
|
159
199
|
end
|
160
200
|
|
161
|
-
def
|
201
|
+
def canonical_type(x)
|
162
202
|
case x
|
163
203
|
when /\A(uint|int)(\[.*\])?\z/
|
164
204
|
"#{$1}256#{$2}"
|
165
|
-
when /\A(
|
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
|