web3-eth 0.1.0 → 0.2.0

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.
data/README.md CHANGED
@@ -1,7 +1,17 @@
1
1
  # Web3 RPC client for Ethereum node
2
2
 
3
3
  This Ruby Gem is used to connect and communicate with Ethereum node ( geth, parity, etc),
4
- having RPC interface.
4
+ having RPC interface. Also it has support to call Etherscan API.
5
+
6
+ What you can do using this GEM:
7
+
8
+ - use Web3 eth interface methods to read blocks, transactions
9
+ - query Etherscan to get ABI for validated contracts
10
+ - parse transaction method calls according to ABI
11
+ - parse transaction logs according to ABI
12
+ - parse contract creation arguments accroding to ABI
13
+ - list internal transaction using parity extended trace JSONRPC API
14
+
5
15
 
6
16
  ## Installation
7
17
 
@@ -21,6 +31,8 @@ Or install it yourself as:
21
31
 
22
32
  ## Usage
23
33
 
34
+ ### Connect
35
+
24
36
  Connecting to local node ( or by SSH Tunnel )
25
37
 
26
38
  ```ruby
@@ -32,10 +44,12 @@ To connect to remote Ethereum node, follow instructions: https://github.com/pari
32
44
  If you need to connect to remote host, you can specify host, port and HTTP connection options:
33
45
 
34
46
  ```ruby
35
- web3 = Web3::Eth::Rpc.new 'node.host.com', 8545, { use_ssl: true, read_timeout: 120 }
47
+ web3 = Web3::Eth::Rpc.new host: 'node.host.com',
48
+ port: 8545,
49
+ connect_options: { use_ssl: true, read_timeout: 120 }
36
50
  ```
37
51
 
38
- Calling eth interface:
52
+ ### Calling eth interface
39
53
 
40
54
  ```
41
55
  >> web3.eth.blockNumber
@@ -62,6 +76,104 @@ Calling eth interface:
62
76
  ```
63
77
 
64
78
 
79
+ ### Calling Etherscan API:
80
+
81
+ ```
82
+ api = Web3::Eth::Etherscan.new 'Your API Key'
83
+ abi = api.contract_getabi address: '0xe3fedaecd47aa8eab6b23227b0ee56f092c967a9'
84
+ ```
85
+
86
+ Method name for Etherscan must be constructed as <module>_<action>, for example contract_getabi
87
+ calls method getabi in module contract
88
+
89
+ If method accepts only one parameter address, the call can be simplified to:
90
+
91
+ ```
92
+ abi = api.contract_getabi '0xe3fedaecd47aa8eab6b23227b0ee56f092c967a9'
93
+ ```
94
+
95
+ ### Accesing contract methods
96
+
97
+ ```
98
+ # creation of contract object
99
+ myContract = web3.eth.contract(abi);
100
+
101
+ # initiate contract for an address
102
+ myContractInstance = myContract.at('0x2ad180cbaffbc97237f572148fc1b283b68d8861');
103
+
104
+ # call constant function
105
+ result = myContractInstance.balanceOf('0x...'); # any constant method works
106
+ puts result
107
+ ```
108
+
109
+ or using Etherscan API ( requires contract ABI be published in Etherescan ):
110
+
111
+ ```
112
+ api = Web3::Eth::Etherscan.new 'Your API Key'
113
+ myContractInstance = web3.eth.load_contract(api, '0x2ad180cbaffbc97237f572148fc1b283b68d8861')
114
+
115
+ // call constant function
116
+ result = myContractInstance.balanceOf('0x....'); # any constant method works
117
+ puts result // '0x25434534534'
118
+ ```
119
+
120
+
121
+ ### Parsing transaction call arguments
122
+
123
+ Method parse_call_args parses call arguments according to ABI.
124
+ Code example is:
125
+
126
+ ```
127
+ api = Web3::Eth::Etherscan.new 'Your API Key'
128
+ abi = api.contract_getabi address: '0x2ad180cbaffbc97237f572148fc1b283b68d8861'
129
+
130
+ myContract = web3.eth.contract(abi);
131
+ tx = web3.eth.getTransactionByHash '0x83da408b05061a2512fe1abf065b37a6aad9ae96d604b288a3da34bf9f1af9e6'
132
+ myContract.parse_call_args tx
133
+ ```
134
+
135
+ ### Parsing smart contract constructor arguments
136
+
137
+ Method parse_constructor_args parses smart contract creation arguments according to ABI.
138
+ Code example is:
139
+
140
+ ```
141
+ api = Web3::Eth::Etherscan.new 'Your API Key'
142
+ abi = api.contract_getabi address: '0xf4702b0918a8a89dfc38459ce42198834818f26b'
143
+
144
+ myContract = web3.eth.contract(abi);
145
+ tx = web3.eth.getTransactionByHash '0x35f0cf1d1c7ec14dd40fe3949d1c535ec3f3953f118cb9dc1394370f966cf957'
146
+ myContract.parse_constructor_args tx
147
+ ```
148
+
149
+ ### Parsing transaction logs
150
+
151
+ Method parse_log_args parses indexed and not-indexed log event arguments according to ABI.
152
+ Code example is:
153
+
154
+ ```
155
+ api = Web3::Eth::Etherscan.new 'Your API Key'
156
+ abi = api.contract_getabi address: '0x2ad180cbaffbc97237f572148fc1b283b68d8861'
157
+
158
+ myContract = web3.eth.contract(abi);
159
+ tx_receipt = web3.eth.getTransactionReceipt '0x83da408b05061a2512fe1abf065b37a6aad9ae96d604b288a3da34bf9f1af9e6'
160
+ myContract.parse_log_args tx_receipt.logs.first
161
+ ```
162
+
163
+ ### Listing internal transactions
164
+
165
+ To use this feature, you should run parity node with the option
166
+ --tracing on, refer to [https://github.com/paritytech/parity/wiki/Configuring-Parity#cli-options].
167
+
168
+ ```
169
+ calls = web3.trace.internalCallsByHash '0x7ac18a1640e443cd069ff51da382b92b585e42dae8f38db0932380bfe86908a6'
170
+ puts calls.first.from
171
+ puts calls.first.to
172
+ puts calls.first.value_eth
173
+ ```
174
+
175
+
176
+
65
177
  ## Development
66
178
 
67
179
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,370 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'web3/eth/abi/type'
4
+ require 'web3/eth/abi/constant'
5
+ require 'web3/eth/abi/exceptions'
6
+ require 'web3/eth/abi/utils'
7
+
8
+ module Web3::Eth::Abi
9
+
10
+ ##
11
+ # Contract ABI encoding and decoding.
12
+ #
13
+ # @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
14
+ #
15
+ module AbiCoder
16
+
17
+ extend self
18
+
19
+ include Constant
20
+
21
+ class EncodingError < StandardError; end
22
+ class DecodingError < StandardError; end
23
+ class ValueOutOfBounds < ValueError; end
24
+
25
+ ##
26
+ # Encodes multiple arguments using the head/tail mechanism.
27
+ #
28
+ def encode_abi(types, args)
29
+ parsed_types = types.map {|t| Type.parse(t) }
30
+
31
+ head_size = (0...args.size)
32
+ .map {|i| parsed_types[i].size || 32 }
33
+ .reduce(0, &:+)
34
+
35
+ head, tail = '', ''
36
+ args.each_with_index do |arg, i|
37
+ if parsed_types[i].dynamic?
38
+ head += encode_type(Type.size_type, head_size + tail.size)
39
+ tail += encode_type(parsed_types[i], arg)
40
+ else
41
+ head += encode_type(parsed_types[i], arg)
42
+ end
43
+ end
44
+
45
+ "#{head}#{tail}"
46
+ end
47
+ alias :encode :encode_abi
48
+
49
+ ##
50
+ # Encodes a single value (static or dynamic).
51
+ #
52
+ # @param type [Ethereum::ABI::Type] value type
53
+ # @param arg [Object] value
54
+ #
55
+ # @return [String] encoded bytes
56
+ #
57
+ def encode_type(type, arg)
58
+ if %w(string bytes).include?(type.base) && type.sub.empty?
59
+ encode_primitive_type type, arg
60
+ elsif type.dynamic?
61
+ raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
62
+
63
+ head, tail = '', ''
64
+ if type.dims.last == 0
65
+ head += encode_type(Type.size_type, arg.size)
66
+ else
67
+ raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last
68
+ end
69
+
70
+ sub_type = type.subtype
71
+ sub_size = type.subtype.size
72
+ arg.size.times do |i|
73
+ if sub_size.nil?
74
+ head += encode_type(Type.size_type, 32*arg.size + tail.size)
75
+ tail += encode_type(sub_type, arg[i])
76
+ else
77
+ head += encode_type(sub_type, arg[i])
78
+ end
79
+ end
80
+
81
+ "#{head}#{tail}"
82
+ else # static type
83
+ if type.dims.empty?
84
+ encode_primitive_type type, arg
85
+ else
86
+ arg.map {|x| encode_type(type.subtype, x) }.join
87
+ end
88
+ end
89
+ end
90
+
91
+ def encode_primitive_type(type, arg)
92
+ case type.base
93
+ when 'uint'
94
+ begin
95
+ real_size = type.sub.to_i
96
+ i = get_uint arg
97
+
98
+ raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
99
+ Utils.zpad_int i
100
+ rescue EncodingError
101
+ raise ValueOutOfBounds, arg
102
+ end
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
+ begin
108
+ real_size = type.sub.to_i
109
+ i = get_int arg
110
+
111
+ raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
112
+ Utils.zpad_int(i % 2**type.sub.to_i)
113
+ rescue EncodingError
114
+ raise ValueOutOfBounds, arg
115
+ end
116
+ when 'ufixed'
117
+ high, low = type.sub.split('x').map(&:to_i)
118
+
119
+ raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**high
120
+ Utils.zpad_int((arg * 2**low).to_i)
121
+ when 'fixed'
122
+ high, low = type.sub.split('x').map(&:to_i)
123
+
124
+ raise ValueOutOfBounds, arg unless arg >= -2**(high - 1) && arg < 2**(high - 1)
125
+
126
+ i = (arg * 2**low).to_i
127
+ Utils.zpad_int(i % 2**(high+low))
128
+ when 'string'
129
+ if arg.encoding.name == 'UTF-8'
130
+ arg = arg.b
131
+ else
132
+ begin
133
+ arg.unpack('U*')
134
+ rescue ArgumentError
135
+ raise ValueError, "string must be UTF-8 encoded"
136
+ end
137
+ end
138
+
139
+ if type.sub.empty? # variable length type
140
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
141
+ size = Utils.zpad_int arg.size
142
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
143
+ "#{size}#{value}"
144
+ else # fixed length type
145
+ sub = type.sub.to_i
146
+ raise ValueOutOfBounds, "invalid string length #{sub}" if arg.size > sub
147
+ raise ValueOutOfBounds, "invalid string length #{sub}" if sub < 0 || sub > 32
148
+ Utils.rpad(arg, BYTE_ZERO, 32)
149
+ end
150
+ when 'bytes'
151
+ raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
152
+ arg = arg.b
153
+
154
+ if type.sub.empty? # variable length type
155
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
156
+ size = Utils.zpad_int arg.size
157
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
158
+ "#{size}#{value}"
159
+ else # fixed length type
160
+ sub = type.sub.to_i
161
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if arg.size > sub
162
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if sub < 0 || sub > 32
163
+ Utils.rpad(arg, BYTE_ZERO, 32)
164
+ end
165
+ when 'hash'
166
+ size = type.sub.to_i
167
+ raise EncodingError, "too long: #{arg}" unless size > 0 && size <= 32
168
+
169
+ if arg.is_a?(Integer)
170
+ Utils.zpad_int(arg)
171
+ elsif arg.size == size
172
+ Utils.zpad arg, 32
173
+ elsif arg.size == size * 2
174
+ Utils.zpad_hex arg
175
+ else
176
+ raise EncodingError, "Could not parse hash: #{arg}"
177
+ end
178
+ when 'address'
179
+ if arg.is_a?(Integer)
180
+ Utils.zpad_int arg
181
+ elsif arg.size == 20
182
+ Utils.zpad arg, 32
183
+ elsif arg.size == 40
184
+ Utils.zpad_hex arg
185
+ elsif arg.size == 42 && arg[0,2] == '0x'
186
+ Utils.zpad_hex arg[2..-1]
187
+ else
188
+ raise EncodingError, "Could not parse address: #{arg}"
189
+ end
190
+ else
191
+ raise EncodingError, "Unhandled type: #{type.base} #{type.sub}"
192
+ end
193
+ end
194
+
195
+ ##
196
+ # Decodes multiple arguments using the head/tail mechanism.
197
+ #
198
+ def decode_abi(types, data)
199
+ parsed_types = types.map {|t| Type.parse(t) }
200
+
201
+ outputs = [nil] * types.size
202
+ start_positions = [nil] * types.size + [data.size]
203
+
204
+ # TODO: refactor, a reverse iteration will be better
205
+ pos = 0
206
+ parsed_types.each_with_index do |t, i|
207
+ # If a type is static, grab the data directly, otherwise record its
208
+ # start position
209
+ if t.dynamic?
210
+ start_positions[i] = Utils.big_endian_to_int(data[pos, 32])
211
+
212
+ j = i - 1
213
+ while j >= 0 && start_positions[j].nil?
214
+ start_positions[j] = start_positions[i]
215
+ j -= 1
216
+ end
217
+
218
+ pos += 32
219
+ else
220
+ outputs[i] = data[pos, t.size]
221
+ pos += t.size
222
+ end
223
+ end
224
+
225
+ # We add a start position equal to the length of the entire data for
226
+ # convenience.
227
+ j = types.size - 1
228
+ while j >= 0 && start_positions[j].nil?
229
+ start_positions[j] = start_positions[types.size]
230
+ j -= 1
231
+ end
232
+
233
+ raise DecodingError, "Not enough data for head" unless pos <= data.size
234
+
235
+ parsed_types.each_with_index do |t, i|
236
+ if t.dynamic?
237
+ offset, next_offset = start_positions[i, 2]
238
+ outputs[i] = data[offset...next_offset]
239
+ end
240
+ end
241
+
242
+ parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) }
243
+ end
244
+ alias :decode :decode_abi
245
+
246
+ def decode_type(type, arg)
247
+ if %w(string bytes).include?(type.base) && type.sub.empty?
248
+ l = Utils.big_endian_to_int arg[0,32]
249
+ data = arg[32..-1]
250
+
251
+ raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Utils.ceil32(l)
252
+
253
+ data[0, l]
254
+ elsif type.dynamic?
255
+ l = Utils.big_endian_to_int arg[0,32]
256
+ subtype = type.subtype
257
+
258
+ if subtype.dynamic?
259
+ raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
260
+
261
+ start_positions = (1..l).map {|i| Utils.big_endian_to_int arg[32*i, 32] }
262
+ start_positions.push arg.size
263
+
264
+ outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
265
+
266
+ outputs.map {|out| decode_type(subtype, out) }
267
+ else
268
+ (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
269
+ end
270
+ elsif !type.dims.empty? # static-sized arrays
271
+ l = type.dims.last[0]
272
+ subtype = type.subtype
273
+
274
+ (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
275
+ else
276
+ decode_primitive_type type, arg
277
+ end
278
+ end
279
+
280
+ def decode_primitive_type(type, data)
281
+ case type.base
282
+ when 'address'
283
+ Utils.encode_hex data[12..-1]
284
+ when 'string', 'bytes'
285
+ if type.sub.empty? # dynamic
286
+ size = Utils.big_endian_to_int data[0,32]
287
+ data[32..-1][0,size]
288
+ else # fixed
289
+ data[0, type.sub.to_i]
290
+ end
291
+ when 'hash'
292
+ data[(32 - type.sub.to_i), type.sub.to_i]
293
+ when 'uint'
294
+ Utils.big_endian_to_int data
295
+ when 'int'
296
+ u = Utils.big_endian_to_int data
297
+ u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
298
+ when 'ufixed'
299
+ high, low = type.sub.split('x').map(&:to_i)
300
+ Utils.big_endian_to_int(data) * 1.0 / 2**low
301
+ when 'fixed'
302
+ high, low = type.sub.split('x').map(&:to_i)
303
+ u = Utils.big_endian_to_int data
304
+ i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
305
+ i * 1.0 / 2**low
306
+ when 'bool'
307
+ data[-1] == BYTE_ONE
308
+ else
309
+ raise DecodingError, "Unknown primitive type: #{type.base}"
310
+ end
311
+ end
312
+
313
+ private
314
+
315
+ def get_uint(n)
316
+ case n
317
+ when Integer
318
+ raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
319
+ n
320
+ when String
321
+ i = if n.size == 40
322
+ Utils.decode_hex(n)
323
+ elsif n.size <= 32
324
+ n
325
+ else
326
+ raise EncodingError, "String too long: #{n}"
327
+ end
328
+ i = Utils.big_endian_to_int i
329
+
330
+ raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
331
+ i
332
+ when true
333
+ 1
334
+ when false, nil
335
+ 0
336
+ else
337
+ raise EncodingError, "Cannot decode uint: #{n}"
338
+ end
339
+ end
340
+
341
+ def get_int(n)
342
+ case n
343
+ when Integer
344
+ raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
345
+ n
346
+ when String
347
+ i = if n.size == 40
348
+ Utils.decode_hex(n)
349
+ elsif n.size <= 32
350
+ n
351
+ else
352
+ raise EncodingError, "String too long: #{n}"
353
+ end
354
+ i = Utils.big_endian_to_int i
355
+
356
+ i = i > INT_MAX ? (i-TT256) : i
357
+ raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
358
+ i
359
+ when true
360
+ 1
361
+ when false, nil
362
+ 0
363
+ else
364
+ raise EncodingError, "Cannot decode int: #{n}"
365
+ end
366
+ end
367
+
368
+ end
369
+
370
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Web3::Eth::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
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Web3::Eth::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
@@ -0,0 +1,116 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Web3::Eth::Abi
4
+ class Type
5
+
6
+ class ParseError < StandardError;
7
+ 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 'fixed', 'ufixed'
32
+ raise ParseError, "Fixed 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, "Fixed size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
38
+ raise ParseError, "Fixed 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