ethereum-abi 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/lib/ethereum/abi.rb +333 -0
- data/lib/ethereum/abi/constant.rb +26 -0
- data/lib/ethereum/abi/contract_translator.rb +184 -0
- data/lib/ethereum/abi/type.rb +117 -0
- data/lib/ethereum/abi/version.rb +5 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5929d9e3f2d9f92e4ee09812a83be6ed5ec8f483
|
4
|
+
data.tar.gz: 9113211fd4457ca8b4d1aaef4f35a2ed02936ce4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 73b55734bf2fbe1a1cc37ff68904869eb54a6c85c574a5062a259e59b6125fb43732cb25dc15925e59468c8b0acf4f6474a45210aa3d2e243547c169130ef360
|
7
|
+
data.tar.gz: 5931f31438e902fc35bc2b2d8c5959c95465698ef6214356da4621df4d65b0a18f7b7f698d82ba70ff8aa83dae029efb3a286a0d5d005d7e8cae2ab2e4c054c1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 zhangyaning
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/lib/ethereum/abi.rb
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'ethereum/abi/contract_translator'
|
2
|
+
require 'ethereum/abi/type'
|
3
|
+
require 'ethereum/abi/constant'
|
4
|
+
require 'ethereum/exceptions'
|
5
|
+
require 'block_logger'
|
6
|
+
require 'rlp'
|
7
|
+
|
8
|
+
module Ethereum
|
9
|
+
##
|
10
|
+
# Contract ABI encoding and decoding.
|
11
|
+
#
|
12
|
+
# @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
13
|
+
#
|
14
|
+
module ABI
|
15
|
+
|
16
|
+
extend self
|
17
|
+
|
18
|
+
include Constant
|
19
|
+
|
20
|
+
class EncodingError < StandardError; end
|
21
|
+
class DecodingError < StandardError; end
|
22
|
+
class ValueOutOfBounds < StandardError; end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Encodes multiple arguments using the head/tail mechanism.
|
26
|
+
#
|
27
|
+
def encode_abi(types, args)
|
28
|
+
parsed_types = types.map {|t| Type.parse(t) }
|
29
|
+
|
30
|
+
head_size = (0...args.size)
|
31
|
+
.map {|i| parsed_types[i].size || 32 }
|
32
|
+
.reduce(0, &:+)
|
33
|
+
|
34
|
+
head, tail = '', ''
|
35
|
+
args.each_with_index do |arg, i|
|
36
|
+
if parsed_types[i].dynamic?
|
37
|
+
head += encode_type(Type.size_type, head_size + tail.size)
|
38
|
+
tail += encode_type(parsed_types[i], arg)
|
39
|
+
else
|
40
|
+
head += encode_type(parsed_types[i], arg)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
"#{head}#{tail}"
|
45
|
+
end
|
46
|
+
alias :encode :encode_abi
|
47
|
+
|
48
|
+
##
|
49
|
+
# Encodes a single value (static or dynamic).
|
50
|
+
#
|
51
|
+
# @param type [Ethereum::ABI::Type] value type
|
52
|
+
# @param arg [Object] value
|
53
|
+
#
|
54
|
+
# @return [String] encoded bytes
|
55
|
+
#
|
56
|
+
def encode_type(type, arg)
|
57
|
+
if %w(string bytes).include?(type.base) && type.sub.empty?
|
58
|
+
raise ArgumentError, "arg must be a string" unless arg.instance_of?(String)
|
59
|
+
|
60
|
+
size = encode_type Type.size_type, arg.size
|
61
|
+
padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size)
|
62
|
+
|
63
|
+
"#{size}#{arg}#{padding}"
|
64
|
+
elsif type.dynamic?
|
65
|
+
raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
|
66
|
+
|
67
|
+
head, tail = '', ''
|
68
|
+
if type.dims.last == 0
|
69
|
+
head += encode_type(Type.size_type, arg.size)
|
70
|
+
else
|
71
|
+
raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last
|
72
|
+
end
|
73
|
+
|
74
|
+
sub_type = type.subtype
|
75
|
+
sub_size = type.subtype.size
|
76
|
+
arg.size.times do |i|
|
77
|
+
if sub_size.nil?
|
78
|
+
head += encode_type(Type.size_type, 32*arg.size + tail.size)
|
79
|
+
tail += encode_type(sub_type, arg[i])
|
80
|
+
else
|
81
|
+
head += encode_type(sub_type, arg[i])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
"#{head}#{tail}"
|
86
|
+
else # static type
|
87
|
+
if type.dims.empty?
|
88
|
+
encode_primitive_type type, arg
|
89
|
+
else
|
90
|
+
arg.map {|x| encode_type(type.subtype, x) }.join
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def encode_primitive_type(type, arg)
|
96
|
+
case type.base
|
97
|
+
when 'uint'
|
98
|
+
real_size = type.sub.to_i
|
99
|
+
i = get_uint arg
|
100
|
+
|
101
|
+
raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
|
102
|
+
Utils.zpad_int i
|
103
|
+
when 'bool'
|
104
|
+
raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
|
105
|
+
Utils.zpad_int(arg ? 1: 0)
|
106
|
+
when 'int'
|
107
|
+
real_size = type.sub.to_i
|
108
|
+
i = get_int arg
|
109
|
+
|
110
|
+
raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
|
111
|
+
Utils.zpad_int(i % 2**type.sub.to_i)
|
112
|
+
when 'ureal', '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 'real', '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', 'bytes'
|
125
|
+
raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
|
126
|
+
|
127
|
+
if type.sub.empty? # variable length type
|
128
|
+
size = Utils.zpad_int arg.size
|
129
|
+
padding = BYTE_ZERO * (Utils.ceil32(arg.size) - arg.size)
|
130
|
+
"#{size}#{arg}#{padding}"
|
131
|
+
else # fixed length type
|
132
|
+
raise ValueOutOfBounds, arg unless arg.size <= type.sub.to_i
|
133
|
+
|
134
|
+
padding = BYTE_ZERO * (32 - arg.size)
|
135
|
+
"#{arg}#{padding}"
|
136
|
+
end
|
137
|
+
when 'hash'
|
138
|
+
size = type.sub.to_i
|
139
|
+
raise EncodingError, "too long: #{arg}" unless size > 0 && size <= 32
|
140
|
+
|
141
|
+
if arg.is_a?(Integer)
|
142
|
+
Utils.zpad_int(arg)
|
143
|
+
elsif arg.size == size
|
144
|
+
Utils.zpad arg, 32
|
145
|
+
elsif arg.size == size * 2
|
146
|
+
Utils.zpad_hex arg
|
147
|
+
else
|
148
|
+
raise EncodingError, "Could not parse hash: #{arg}"
|
149
|
+
end
|
150
|
+
when 'address'
|
151
|
+
if arg.is_a?(Integer)
|
152
|
+
Utils.zpad_int arg
|
153
|
+
elsif arg.size == 20
|
154
|
+
Utils.zpad arg, 32
|
155
|
+
elsif arg.size == 40
|
156
|
+
Utils.zpad_hex arg
|
157
|
+
elsif arg.size == 42 && arg[0,2] == '0x'
|
158
|
+
Utils.zpad_hex arg[2..-1]
|
159
|
+
else
|
160
|
+
raise EncodingError, "Could not parse address: #{arg}"
|
161
|
+
end
|
162
|
+
else
|
163
|
+
raise EncodingError, "Unhandled type: #{type.base} #{type.sub}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Decodes multiple arguments using the head/tail mechanism.
|
169
|
+
#
|
170
|
+
def decode_abi(types, data)
|
171
|
+
parsed_types = types.map {|t| Type.parse(t) }
|
172
|
+
|
173
|
+
outputs = [nil] * types.size
|
174
|
+
start_positions = [nil] * types.size + [data.size]
|
175
|
+
|
176
|
+
# TODO: refactor, a reverse iteration will be better
|
177
|
+
pos = 0
|
178
|
+
parsed_types.each_with_index do |t, i|
|
179
|
+
# If a type is static, grab the data directly, otherwise record its
|
180
|
+
# start position
|
181
|
+
if t.dynamic?
|
182
|
+
start_positions[i] = Utils.big_endian_to_int(data[pos, 32])
|
183
|
+
|
184
|
+
j = i - 1
|
185
|
+
while j >= 0 && start_positions[j].nil?
|
186
|
+
start_positions[j] = start_positions[i]
|
187
|
+
j -= 1
|
188
|
+
end
|
189
|
+
|
190
|
+
pos += 32
|
191
|
+
else
|
192
|
+
outputs[i] = data[pos, t.size]
|
193
|
+
pos += t.size
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# We add a start position equal to the length of the entire data for
|
198
|
+
# convenience.
|
199
|
+
j = types.size - 1
|
200
|
+
while j >= 0 && start_positions[j].nil?
|
201
|
+
start_positions[j] = start_positions[types.size]
|
202
|
+
j -= 1
|
203
|
+
end
|
204
|
+
|
205
|
+
raise DecodingError, "Not enough data for head" unless pos <= data.size
|
206
|
+
|
207
|
+
parsed_types.each_with_index do |t, i|
|
208
|
+
if t.dynamic?
|
209
|
+
offset, next_offset = start_positions[i, 2]
|
210
|
+
outputs[i] = data[offset...next_offset]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) }
|
215
|
+
end
|
216
|
+
alias :decode :decode_abi
|
217
|
+
|
218
|
+
def decode_type(type, arg)
|
219
|
+
if %w(string bytes).include?(type.base) && type.sub.empty?
|
220
|
+
l = Utils.big_endian_to_int arg[0,32]
|
221
|
+
data = arg[32..-1]
|
222
|
+
|
223
|
+
raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Utils.ceil32(l)
|
224
|
+
|
225
|
+
data[0, l]
|
226
|
+
elsif type.dynamic?
|
227
|
+
l = Utils.big_endian_to_int arg[0,32]
|
228
|
+
subtype = type.subtype
|
229
|
+
|
230
|
+
if subtype.dynamic?
|
231
|
+
raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
|
232
|
+
|
233
|
+
start_positions = (1..l).map {|i| Utils.big_endian_to_int arg[32*i, 32] }
|
234
|
+
start_positions.push arg.size
|
235
|
+
|
236
|
+
outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
|
237
|
+
|
238
|
+
outputs.map {|out| decode_type(subtype, out) }
|
239
|
+
else
|
240
|
+
(0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
|
241
|
+
end
|
242
|
+
elsif !type.dims.empty? # static-sized arrays
|
243
|
+
l = type.dims.last[0]
|
244
|
+
subtype = type.subtype
|
245
|
+
|
246
|
+
(0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
|
247
|
+
else
|
248
|
+
decode_primitive_type type, arg
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def decode_primitive_type(type, data)
|
253
|
+
case type.base
|
254
|
+
when 'address'
|
255
|
+
Utils.encode_hex data[12..-1]
|
256
|
+
when 'string', 'bytes'
|
257
|
+
if type.sub.empty? # dynamic
|
258
|
+
size = Utils.big_endian_to_int data[0,32]
|
259
|
+
data[32..-1][0,size]
|
260
|
+
else # fixed
|
261
|
+
data[0, type.sub.to_i]
|
262
|
+
end
|
263
|
+
when 'hash'
|
264
|
+
data[(32 - type.sub.to_i), type.sub.to_i]
|
265
|
+
when 'uint'
|
266
|
+
Utils.big_endian_to_int data
|
267
|
+
when 'int'
|
268
|
+
u = Utils.big_endian_to_int data
|
269
|
+
u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
|
270
|
+
when 'ureal', 'ufixed'
|
271
|
+
high, low = type.sub.split('x').map(&:to_i)
|
272
|
+
Utils.big_endian_to_int(data) * 1.0 / 2**low
|
273
|
+
when 'real', 'fixed'
|
274
|
+
high, low = type.sub.split('x').map(&:to_i)
|
275
|
+
u = Utils.big_endian_to_int data
|
276
|
+
i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
|
277
|
+
i * 1.0 / 2**low
|
278
|
+
when 'bool'
|
279
|
+
data[-1] == BYTE_ONE
|
280
|
+
else
|
281
|
+
raise DecodingError, "Unknown primitive type: #{type.base}"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def get_uint(n)
|
288
|
+
case n
|
289
|
+
when Integer
|
290
|
+
raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
|
291
|
+
n
|
292
|
+
when String
|
293
|
+
if n.size == 40
|
294
|
+
Utils.big_endian_to_int Utils.decode_hex(n)
|
295
|
+
elsif n.size <= 32
|
296
|
+
Utils.big_endian_to_int n
|
297
|
+
else
|
298
|
+
raise EncodingError, "String too long: #{n}"
|
299
|
+
end
|
300
|
+
when true
|
301
|
+
1
|
302
|
+
when false, nil
|
303
|
+
0
|
304
|
+
else
|
305
|
+
raise EncodingError, "Cannot decode uint: #{n}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def get_int(n)
|
310
|
+
case n
|
311
|
+
when Integer
|
312
|
+
raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
|
313
|
+
n
|
314
|
+
when String
|
315
|
+
if n.size == 40
|
316
|
+
i = Utils.big_endian_to_int Utils.decode_hex(n)
|
317
|
+
elsif n.size <= 32
|
318
|
+
i = Utils.big_endian_to_int n
|
319
|
+
else
|
320
|
+
raise EncodingError, "String too long: #{n}"
|
321
|
+
end
|
322
|
+
i > INT_MAX ? (i-TT256) : i
|
323
|
+
when true
|
324
|
+
1
|
325
|
+
when false, nil
|
326
|
+
0
|
327
|
+
else
|
328
|
+
raise EncodingError, "Cannot decode int: #{n}"
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
module ABI
|
5
|
+
module Constant
|
6
|
+
BYTE_EMPTY = "".freeze
|
7
|
+
BYTE_ZERO = "\x00".freeze
|
8
|
+
BYTE_ONE = "\x01".freeze
|
9
|
+
|
10
|
+
TT32 = 2**32
|
11
|
+
TT256 = 2**256
|
12
|
+
TT64M1 = 2**64 - 1
|
13
|
+
|
14
|
+
UINT_MAX = 2**256 - 1
|
15
|
+
UINT_MIN = 0
|
16
|
+
INT_MAX = 2**255 - 1
|
17
|
+
INT_MIN = -2**255
|
18
|
+
|
19
|
+
HASH_ZERO = ("\x00"*32).freeze
|
20
|
+
|
21
|
+
PUBKEY_ZERO = ("\x00"*32).freeze
|
22
|
+
PRIVKEY_ZERO = ("\x00"*32).freeze
|
23
|
+
PRIVKEY_ZERO_HEX = ('0'*64).freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Ethereum
|
6
|
+
module ABI
|
7
|
+
class ContractTranslator
|
8
|
+
|
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,
|
16
|
+
function_data: {},
|
17
|
+
event_data: {}
|
18
|
+
}
|
19
|
+
|
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] = {
|
31
|
+
prefix: method_id(name, encode_types),
|
32
|
+
encode_types: encode_types,
|
33
|
+
decode_types: decode_types,
|
34
|
+
is_constant: desc.fetch('constant', false),
|
35
|
+
signature: signature
|
36
|
+
}
|
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)] = {
|
42
|
+
types: encode_types,
|
43
|
+
name: name,
|
44
|
+
names: names,
|
45
|
+
indexed: indexed,
|
46
|
+
anonymous: desc.fetch('anonymous', false)
|
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}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
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
|
+
#
|
72
|
+
def encode(name, args)
|
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}"
|
80
|
+
end
|
81
|
+
|
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
|
87
|
+
|
88
|
+
ABI.encode_abi constructor_data[:encode_types], args
|
89
|
+
end
|
90
|
+
|
91
|
+
def decode(name, data)
|
92
|
+
desc = function_data[name]
|
93
|
+
ABI.decode_abi desc[:decode_types], data
|
94
|
+
end
|
95
|
+
|
96
|
+
def constructor_data
|
97
|
+
@contract[:constructor_data]
|
98
|
+
end
|
99
|
+
|
100
|
+
def function_data
|
101
|
+
@contract[:function_data]
|
102
|
+
end
|
103
|
+
|
104
|
+
def event_data
|
105
|
+
@contract[:event_data]
|
106
|
+
end
|
107
|
+
|
108
|
+
def function(name)
|
109
|
+
function_data[name]
|
110
|
+
end
|
111
|
+
|
112
|
+
def event(name, encode_types)
|
113
|
+
event_data[event_id(name, encode_types)]
|
114
|
+
end
|
115
|
+
|
116
|
+
def listen(log, noprint: false)
|
117
|
+
return if log.topics.size == 0 || !event_data.has_key?(log.topics[0])
|
118
|
+
|
119
|
+
data = event_data[log.topics[0]]
|
120
|
+
types = data[:types]
|
121
|
+
name = data[:name]
|
122
|
+
names = data[:names]
|
123
|
+
indexed = data[:indexed]
|
124
|
+
indexed_types = types.zip(indexed).select {|(t, i)| i.true? }.map(&:first)
|
125
|
+
unindexed_types = types.zip(indexed).select {|(t, i)| i.false? }.map(&:first)
|
126
|
+
|
127
|
+
deserialized_args = ABI.decode_abi unindexed_types, log.data
|
128
|
+
|
129
|
+
o = {}
|
130
|
+
c1, c2 = 0, 0
|
131
|
+
names.each_with_index do |n, i|
|
132
|
+
if indexed[i].true?
|
133
|
+
topic_bytes = Utils.zpad_int log.topics[c1+1]
|
134
|
+
o[n] = ABI.decode_primitive_type ABI::Type.parse(indexed_types[c1]), topic_bytes
|
135
|
+
c1 += 1
|
136
|
+
else
|
137
|
+
o[n] = deserialized_args[c2]
|
138
|
+
c2 += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
o['_event_type'] = name
|
143
|
+
p o unless noprint
|
144
|
+
|
145
|
+
o
|
146
|
+
end
|
147
|
+
|
148
|
+
def method_id(name, encode_types)
|
149
|
+
Utils.big_endian_to_int Utils.keccak256(get_sig(name, encode_types))[0,4]
|
150
|
+
end
|
151
|
+
|
152
|
+
def event_id(name, encode_types)
|
153
|
+
Utils.big_endian_to_int Utils.keccak256(get_sig(name, encode_types))
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def logger
|
159
|
+
@logger ||= Logger.new 'eth.abi.contract_translator'
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_sig(name, encode_types)
|
163
|
+
"#{name}(#{encode_types.map {|x| canonical_name(x) }.join(',')})"
|
164
|
+
end
|
165
|
+
|
166
|
+
def canonical_name(x)
|
167
|
+
case x
|
168
|
+
when /\A(uint|int)(\[.*\])?\z/
|
169
|
+
"#{$1}256#{$2}"
|
170
|
+
when /\A(real|ureal|fixed|ufixed)(\[.*\])?\z/
|
171
|
+
"#{$1}128x128#{$2}"
|
172
|
+
else
|
173
|
+
x
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def basename(n)
|
178
|
+
i = n.index '('
|
179
|
+
i ? n[0,i] : n
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
module ABI
|
5
|
+
class Type
|
6
|
+
|
7
|
+
class ParseError < StandardError; end
|
8
|
+
|
9
|
+
class <<self
|
10
|
+
##
|
11
|
+
# Crazy regexp to seperate out base type component (eg. uint), size (eg.
|
12
|
+
# 256, 128x128, nil), array component (eg. [], [45], nil)
|
13
|
+
#
|
14
|
+
def parse(type)
|
15
|
+
_, base, sub, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
|
16
|
+
|
17
|
+
dims = dimension.scan(/\[[0-9]*\]/)
|
18
|
+
raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
|
19
|
+
|
20
|
+
case base
|
21
|
+
when 'string'
|
22
|
+
raise ParseError, "String type must have no suffix or numerical suffix" unless sub.empty?
|
23
|
+
when 'bytes'
|
24
|
+
raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
|
25
|
+
when 'uint', 'int'
|
26
|
+
raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
|
27
|
+
|
28
|
+
size = sub.to_i
|
29
|
+
raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
|
30
|
+
raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
|
31
|
+
when 'ureal', 'real', 'fixed', 'ufixed'
|
32
|
+
raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
|
33
|
+
|
34
|
+
high, low = sub.split('x').map(&:to_i)
|
35
|
+
total = high + low
|
36
|
+
|
37
|
+
raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
|
38
|
+
raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
|
39
|
+
when 'hash'
|
40
|
+
raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
|
41
|
+
when 'address'
|
42
|
+
raise ParseError, "Address cannot have suffix" unless sub.empty?
|
43
|
+
when 'bool'
|
44
|
+
raise ParseError, "Bool cannot have suffix" unless sub.empty?
|
45
|
+
else
|
46
|
+
raise ParseError, "Unrecognized type base: #{base}"
|
47
|
+
end
|
48
|
+
|
49
|
+
new(base, sub, dims.map {|x| x[1...-1].to_i })
|
50
|
+
end
|
51
|
+
|
52
|
+
def size_type
|
53
|
+
@size_type ||= new('uint', 256, [])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr :base, :sub, :dims
|
58
|
+
|
59
|
+
##
|
60
|
+
# @param base [String] base name of type, e.g. uint for uint256[4]
|
61
|
+
# @param sub [String] subscript of type, e.g. 256 for uint256[4]
|
62
|
+
# @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
|
63
|
+
# for uint256[1][2][], [] for non-array type
|
64
|
+
#
|
65
|
+
def initialize(base, sub, dims)
|
66
|
+
@base = base
|
67
|
+
@sub = sub
|
68
|
+
@dims = dims
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(another_type)
|
72
|
+
base == another_type.base &&
|
73
|
+
sub == another_type.sub &&
|
74
|
+
dims == another_type.dims
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Get the static size of a type, or nil if dynamic.
|
79
|
+
#
|
80
|
+
# @return [Integer, NilClass] size of static type, or nil for dynamic
|
81
|
+
# type
|
82
|
+
#
|
83
|
+
def size
|
84
|
+
@size ||= if dims.empty?
|
85
|
+
if %w(string bytes).include?(base) && sub.empty?
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
32
|
89
|
+
end
|
90
|
+
else
|
91
|
+
if dims.last == 0 # 0 for dynamic array []
|
92
|
+
nil
|
93
|
+
else
|
94
|
+
subtype.dynamic? ? nil : dims.last * subtype.size
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def dynamic?
|
100
|
+
size.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Type with one dimension lesser.
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# Type.parse("uint256[2][]").subtype # => Type.new('uint', 256, [2])
|
108
|
+
#
|
109
|
+
# @return [Ethereum::ABI::Type]
|
110
|
+
#
|
111
|
+
def subtype
|
112
|
+
@subtype ||= self.class.new(base, sub, dims[0...-1])
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ethereum-abi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zhang.Ya.Ning
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ethereum-base
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: block_logger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.2
|
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.7.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.7.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 5.8.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 5.8.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.8.7.6
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.8.7.6
|
97
|
+
description: Ethereum ABI implementation in ruby.
|
98
|
+
email:
|
99
|
+
- zhangyaning1985@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- LICENSE
|
105
|
+
- README.md
|
106
|
+
- lib/ethereum/abi.rb
|
107
|
+
- lib/ethereum/abi/constant.rb
|
108
|
+
- lib/ethereum/abi/contract_translator.rb
|
109
|
+
- lib/ethereum/abi/type.rb
|
110
|
+
- lib/ethereum/abi/version.rb
|
111
|
+
homepage: https://github.com/u2/ethereum-abi
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.4.5
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Ethereum ABI, ruby version.
|
135
|
+
test_files: []
|
136
|
+
has_rdoc:
|