web3ethereum 1.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +215 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/web3/eth.rb +13 -0
- data/lib/web3/eth/abi/abi_coder.rb +375 -0
- data/lib/web3/eth/abi/constant.rb +30 -0
- data/lib/web3/eth/abi/exceptions.rb +28 -0
- data/lib/web3/eth/abi/type.rb +118 -0
- data/lib/web3/eth/abi/utils.rb +224 -0
- data/lib/web3/eth/block.rb +33 -0
- data/lib/web3/eth/call_trace.rb +87 -0
- data/lib/web3/eth/contract.rb +171 -0
- data/lib/web3/eth/eth_module.rb +52 -0
- data/lib/web3/eth/etherscan.rb +71 -0
- data/lib/web3/eth/log.rb +34 -0
- data/lib/web3/eth/rpc.rb +71 -0
- data/lib/web3/eth/trace_module.rb +26 -0
- data/lib/web3/eth/transaction.rb +73 -0
- data/lib/web3/eth/transaction_receipt.rb +41 -0
- data/lib/web3/eth/utility.rb +24 -0
- data/lib/web3/eth/version.rb +5 -0
- data/web3eth.gemspec +39 -0
- metadata +107 -0
data/lib/web3/eth.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "web3/eth/version"
|
2
|
+
require "web3/eth/abi/abi_coder"
|
3
|
+
require "web3/eth/utility"
|
4
|
+
require "web3/eth/block"
|
5
|
+
require "web3/eth/transaction"
|
6
|
+
require "web3/eth/contract"
|
7
|
+
require "web3/eth/call_trace"
|
8
|
+
require "web3/eth/log"
|
9
|
+
require "web3/eth/transaction_receipt"
|
10
|
+
require "web3/eth/eth_module"
|
11
|
+
require "web3/eth/trace_module"
|
12
|
+
require "web3/eth/etherscan"
|
13
|
+
require "web3/eth/rpc"
|
@@ -0,0 +1,375 @@
|
|
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_typed_data type_name, data
|
247
|
+
decode_primitive_type Type.parse(type_name), data
|
248
|
+
end
|
249
|
+
|
250
|
+
def decode_type(type, arg)
|
251
|
+
if %w(string bytes).include?(type.base) && type.sub.empty?
|
252
|
+
l = Utils.big_endian_to_int arg[0,32]
|
253
|
+
data = arg[32..-1]
|
254
|
+
data[0, l]
|
255
|
+
elsif type.dynamic?
|
256
|
+
l = Utils.big_endian_to_int arg[0,32]
|
257
|
+
subtype = type.subtype
|
258
|
+
|
259
|
+
if subtype.dynamic?
|
260
|
+
raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
|
261
|
+
|
262
|
+
start_positions = (1..l).map {|i| Utils.big_endian_to_int arg[32*i, 32] }
|
263
|
+
start_positions.push arg.size
|
264
|
+
|
265
|
+
outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
|
266
|
+
|
267
|
+
outputs.map {|out| decode_type(subtype, out) }
|
268
|
+
else
|
269
|
+
(0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
|
270
|
+
end
|
271
|
+
elsif !type.dims.empty? # static-sized arrays
|
272
|
+
l = type.dims.last
|
273
|
+
subtype = type.subtype
|
274
|
+
|
275
|
+
(0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
|
276
|
+
else
|
277
|
+
decode_primitive_type type, arg
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def decode_primitive_type(type, data)
|
282
|
+
case type.base
|
283
|
+
when 'address'
|
284
|
+
Utils.encode_hex data[12..-1]
|
285
|
+
when 'string', 'bytes'
|
286
|
+
if type.sub.empty? # dynamic
|
287
|
+
if data.length==32
|
288
|
+
data[0..32]
|
289
|
+
else
|
290
|
+
size = Utils.big_endian_to_int data[0,32]
|
291
|
+
data[32..-1][0,size]
|
292
|
+
end
|
293
|
+
else # fixed
|
294
|
+
data[0, type.sub.to_i]
|
295
|
+
end
|
296
|
+
when 'hash'
|
297
|
+
data[(32 - type.sub.to_i), type.sub.to_i]
|
298
|
+
when 'uint'
|
299
|
+
Utils.big_endian_to_int data
|
300
|
+
when 'int'
|
301
|
+
u = Utils.big_endian_to_int data
|
302
|
+
u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
|
303
|
+
when 'ufixed'
|
304
|
+
high, low = type.sub.split('x').map(&:to_i)
|
305
|
+
Utils.big_endian_to_int(data) * 1.0 / 2**low
|
306
|
+
when 'fixed'
|
307
|
+
high, low = type.sub.split('x').map(&:to_i)
|
308
|
+
u = Utils.big_endian_to_int data
|
309
|
+
i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
|
310
|
+
i * 1.0 / 2**low
|
311
|
+
when 'bool'
|
312
|
+
data[-1] == BYTE_ONE
|
313
|
+
else
|
314
|
+
raise DecodingError, "Unknown primitive type: #{type.base}"
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
def get_uint(n)
|
321
|
+
case n
|
322
|
+
when Integer
|
323
|
+
raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
|
324
|
+
n
|
325
|
+
when String
|
326
|
+
i = if n.size == 40
|
327
|
+
Utils.decode_hex(n)
|
328
|
+
elsif n.size <= 32
|
329
|
+
n
|
330
|
+
else
|
331
|
+
raise EncodingError, "String too long: #{n}"
|
332
|
+
end
|
333
|
+
i = Utils.big_endian_to_int i
|
334
|
+
|
335
|
+
raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
|
336
|
+
i
|
337
|
+
when true
|
338
|
+
1
|
339
|
+
when false, nil
|
340
|
+
0
|
341
|
+
else
|
342
|
+
raise EncodingError, "Cannot decode uint: #{n}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def get_int(n)
|
347
|
+
case n
|
348
|
+
when Integer
|
349
|
+
raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
|
350
|
+
n
|
351
|
+
when String
|
352
|
+
i = if n.size == 40
|
353
|
+
Utils.decode_hex(n)
|
354
|
+
elsif n.size <= 32
|
355
|
+
n
|
356
|
+
else
|
357
|
+
raise EncodingError, "String too long: #{n}"
|
358
|
+
end
|
359
|
+
i = Utils.big_endian_to_int i
|
360
|
+
|
361
|
+
i = i > INT_MAX ? (i-TT256) : i
|
362
|
+
raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
|
363
|
+
i
|
364
|
+
when true
|
365
|
+
1
|
366
|
+
when false, nil
|
367
|
+
0
|
368
|
+
else
|
369
|
+
raise EncodingError, "Cannot decode int: #{n}"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
|
375
|
+
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
|