web3-eth 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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