eth 0.5.10 → 0.5.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql.yml +5 -5
- data/.github/workflows/docs.yml +3 -3
- data/.github/workflows/spec.yml +8 -4
- data/CHANGELOG.md +52 -0
- data/eth.gemspec +4 -1
- data/lib/eth/abi/decoder.rb +154 -0
- data/lib/eth/abi/encoder.rb +304 -0
- data/lib/eth/abi/event.rb +37 -5
- data/lib/eth/abi/type.rb +10 -2
- data/lib/eth/abi.rb +10 -385
- data/lib/eth/api.rb +45 -52
- data/lib/eth/chain.rb +9 -0
- data/lib/eth/client/http.rb +18 -4
- data/lib/eth/client/ipc.rb +2 -2
- data/lib/eth/client.rb +71 -133
- data/lib/eth/contract/event.rb +16 -2
- data/lib/eth/contract.rb +11 -1
- data/lib/eth/eip712.rb +5 -1
- data/lib/eth/solidity.rb +7 -4
- data/lib/eth/tx/eip1559.rb +10 -5
- data/lib/eth/tx/eip2930.rb +10 -5
- data/lib/eth/tx/legacy.rb +0 -2
- data/lib/eth/tx.rb +9 -2
- data/lib/eth/util.rb +1 -0
- data/lib/eth/version.rb +11 -2
- metadata +21 -6
- data/lib/eth/client/http_auth.rb +0 -73
data/lib/eth/client/http.rb
CHANGED
@@ -17,7 +17,7 @@ require "net/http"
|
|
17
17
|
# Provides the {Eth} module.
|
18
18
|
module Eth
|
19
19
|
|
20
|
-
# Provides an HTTP/S-RPC client.
|
20
|
+
# Provides an HTTP/S-RPC client with basic authentication.
|
21
21
|
class Client::Http < Client
|
22
22
|
|
23
23
|
# The host of the HTTP endpoint.
|
@@ -32,8 +32,11 @@ module Eth
|
|
32
32
|
# Attribute indicator for SSL.
|
33
33
|
attr_reader :ssl
|
34
34
|
|
35
|
+
# Attribute for user.
|
36
|
+
attr_reader :user
|
37
|
+
|
35
38
|
# Constructor for the HTTP Client. Should not be used; use
|
36
|
-
# {Client.create}
|
39
|
+
# {Client.create} instead.
|
37
40
|
#
|
38
41
|
# @param host [String] an URI pointing to an HTTP RPC-API.
|
39
42
|
def initialize(host)
|
@@ -43,14 +46,20 @@ module Eth
|
|
43
46
|
@host = uri.host
|
44
47
|
@port = uri.port
|
45
48
|
@ssl = uri.scheme == "https"
|
46
|
-
|
49
|
+
if !(uri.user.nil? && uri.password.nil?)
|
50
|
+
@user = uri.user
|
51
|
+
@password = uri.password
|
52
|
+
@uri = URI("#{uri.scheme}://#{uri.user}:#{uri.password}@#{@host}:#{@port}#{uri.path}")
|
53
|
+
else
|
54
|
+
@uri = URI("#{uri.scheme}://#{@host}:#{@port}#{uri.path}")
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
# Sends an RPC request to the connected HTTP client.
|
50
59
|
#
|
51
60
|
# @param payload [Hash] the RPC request parameters.
|
52
61
|
# @return [String] a JSON-encoded response.
|
53
|
-
def
|
62
|
+
def send_request(payload)
|
54
63
|
http = Net::HTTP.new(@host, @port)
|
55
64
|
http.use_ssl = @ssl
|
56
65
|
header = { "Content-Type" => "application/json" }
|
@@ -60,4 +69,9 @@ module Eth
|
|
60
69
|
response.body
|
61
70
|
end
|
62
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Attribute for password.
|
76
|
+
attr_reader :password
|
63
77
|
end
|
data/lib/eth/client/ipc.rb
CHANGED
@@ -24,7 +24,7 @@ module Eth
|
|
24
24
|
attr_accessor :path
|
25
25
|
|
26
26
|
# Constructor for the IPC Client. Should not be used; use
|
27
|
-
# {Client.create}
|
27
|
+
# {Client.create} instead.
|
28
28
|
#
|
29
29
|
# @param path [String] an URI pointing to an IPC RPC-API.
|
30
30
|
def initialize(path)
|
@@ -36,7 +36,7 @@ module Eth
|
|
36
36
|
#
|
37
37
|
# @param payload [Hash] the RPC request parameters.
|
38
38
|
# @return [String] a JSON-encoded response.
|
39
|
-
def
|
39
|
+
def send_request(payload)
|
40
40
|
socket = UNIXSocket.new(@path)
|
41
41
|
socket.puts(payload)
|
42
42
|
read = socket.recvmsg(nil)[0]
|
data/lib/eth/client.rb
CHANGED
@@ -25,7 +25,7 @@ module Eth
|
|
25
25
|
# The connected network's chain ID.
|
26
26
|
attr_reader :chain_id
|
27
27
|
|
28
|
-
# The connected network's client
|
28
|
+
# The connected network's client default account.
|
29
29
|
attr_accessor :default_account
|
30
30
|
|
31
31
|
# The default transaction max priority fee per gas in Wei, defaults to {Tx::DEFAULT_PRIORITY_FEE}.
|
@@ -34,8 +34,8 @@ module Eth
|
|
34
34
|
# The default transaction max fee per gas in Wei, defaults to {Tx::DEFAULT_GAS_PRICE}.
|
35
35
|
attr_accessor :max_fee_per_gas
|
36
36
|
|
37
|
-
# The
|
38
|
-
attr_accessor :
|
37
|
+
# The block number used for archive calls.
|
38
|
+
attr_accessor :block_number
|
39
39
|
|
40
40
|
# A custom error type if a contract interaction fails.
|
41
41
|
class ContractExecutionError < StandardError; end
|
@@ -43,19 +43,16 @@ module Eth
|
|
43
43
|
# Creates a new RPC-Client, either by providing an HTTP/S host or
|
44
44
|
# an IPC path. Supports basic authentication with username and password.
|
45
45
|
#
|
46
|
-
# **Note**, this sets the folling gas defaults: {Tx::DEFAULT_PRIORITY_FEE}
|
47
|
-
# {Tx::DEFAULT_GAS_PRICE
|
48
|
-
# {#
|
49
|
-
# custom values prior to submitting transactions.
|
46
|
+
# **Note**, this sets the folling gas defaults: {Tx::DEFAULT_PRIORITY_FEE}
|
47
|
+
# and {Tx::DEFAULT_GAS_PRICE. Use {#max_priority_fee_per_gas} and
|
48
|
+
# {#max_fee_per_gas} to set custom values prior to submitting transactions.
|
50
49
|
#
|
51
50
|
# @param host [String] either an HTTP/S host or an IPC path.
|
52
51
|
# @return [Eth::Client::Ipc] an IPC client.
|
53
|
-
# @return [Eth::Client::HttpAuth] an HTTP client with basic authentication.
|
54
52
|
# @return [Eth::Client::Http] an HTTP client.
|
55
53
|
# @raise [ArgumentError] in case it cannot determine the client type.
|
56
54
|
def self.create(host)
|
57
55
|
return Client::Ipc.new host if host.end_with? ".ipc"
|
58
|
-
return Client::HttpAuth.new host if Regexp.new(":.*@.*:", Regexp::IGNORECASE).match host
|
59
56
|
return Client::Http.new host if host.start_with? "http"
|
60
57
|
raise ArgumentError, "Unable to detect client type!"
|
61
58
|
end
|
@@ -66,18 +63,17 @@ module Eth
|
|
66
63
|
@id = 0
|
67
64
|
@max_priority_fee_per_gas = Tx::DEFAULT_PRIORITY_FEE
|
68
65
|
@max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
|
69
|
-
@gas_limit = Tx::DEFAULT_GAS_LIMIT
|
70
66
|
end
|
71
67
|
|
72
|
-
# Gets the default account (
|
68
|
+
# Gets the default account (first account) of the connected client.
|
73
69
|
#
|
74
70
|
# **Note**, that many remote providers (e.g., Infura) do not provide
|
75
71
|
# any accounts.
|
76
72
|
#
|
77
|
-
# @return [Eth::Address] the
|
73
|
+
# @return [Eth::Address] the default account address.
|
78
74
|
def default_account
|
79
|
-
raise ArgumentError, "The default account is not available on remote connections!" unless local?
|
80
|
-
@default_account ||= Address.new
|
75
|
+
raise ArgumentError, "The default account is not available on remote connections!" unless local? || @default_account
|
76
|
+
@default_account ||= Address.new eth_accounts["result"].first
|
81
77
|
end
|
82
78
|
|
83
79
|
# Gets the chain ID of the connected network.
|
@@ -115,7 +111,7 @@ module Eth
|
|
115
111
|
end
|
116
112
|
|
117
113
|
# Simply transfer Ether to an account and waits for it to be mined.
|
118
|
-
# Uses `
|
114
|
+
# Uses `eth_accounts` and external signer if no sender key is
|
119
115
|
# provided.
|
120
116
|
#
|
121
117
|
# See {#transfer} for params and overloads.
|
@@ -126,7 +122,7 @@ module Eth
|
|
126
122
|
end
|
127
123
|
|
128
124
|
# Simply transfer Ether to an account without any call data or
|
129
|
-
# access lists attached. Uses `
|
125
|
+
# access lists attached. Uses `eth_accounts` and external signer
|
130
126
|
# if no sender key is provided.
|
131
127
|
#
|
132
128
|
# **Note**, that many remote providers (e.g., Infura) do not provide
|
@@ -146,41 +142,10 @@ module Eth
|
|
146
142
|
params = {
|
147
143
|
value: amount,
|
148
144
|
to: destination,
|
149
|
-
gas_limit:
|
145
|
+
gas_limit: Tx::DEFAULT_GAS_LIMIT,
|
150
146
|
chain_id: chain_id,
|
151
147
|
}
|
152
|
-
|
153
|
-
params.merge!({
|
154
|
-
gas_price: max_fee_per_gas,
|
155
|
-
})
|
156
|
-
else
|
157
|
-
params.merge!({
|
158
|
-
priority_fee: max_priority_fee_per_gas,
|
159
|
-
max_gas_fee: max_fee_per_gas,
|
160
|
-
})
|
161
|
-
end
|
162
|
-
unless kwargs[:sender_key].nil?
|
163
|
-
|
164
|
-
# use the provided key as sender and signer
|
165
|
-
params.merge!({
|
166
|
-
from: kwargs[:sender_key].address,
|
167
|
-
nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
|
168
|
-
})
|
169
|
-
tx = Eth::Tx.new(params)
|
170
|
-
tx.sign kwargs[:sender_key]
|
171
|
-
return eth_send_raw_transaction(tx.hex)["result"]
|
172
|
-
else
|
173
|
-
|
174
|
-
# we do not allow accessing accounts on remote connections
|
175
|
-
raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
|
176
|
-
|
177
|
-
# use the default account as sender and external signer
|
178
|
-
params.merge!({
|
179
|
-
from: default_account,
|
180
|
-
nonce: kwargs[:nonce] || get_nonce(default_account),
|
181
|
-
})
|
182
|
-
return eth_send_transaction(params)["result"]
|
183
|
-
end
|
148
|
+
send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
|
184
149
|
end
|
185
150
|
|
186
151
|
# Transfers a token that implements the ERC20 `transfer()` interface.
|
@@ -207,7 +172,7 @@ module Eth
|
|
207
172
|
# @param amount [Integer] the transfer amount (mind the `decimals()`).
|
208
173
|
# @param **sender_key [Eth::Key] the sender private key.
|
209
174
|
# @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
210
|
-
# @param **gas_limit [Integer] optional gas limit override for
|
175
|
+
# @param **gas_limit [Integer] optional gas limit override for the transfer.
|
211
176
|
# @param **nonce [Integer] optional specific nonce for transaction.
|
212
177
|
# @param **tx_value [Integer] optional transaction value field filling.
|
213
178
|
# @return [Object] returns the result of the transaction.
|
@@ -217,7 +182,7 @@ module Eth
|
|
217
182
|
end
|
218
183
|
|
219
184
|
# Deploys a contract and waits for it to be mined. Uses
|
220
|
-
# `
|
185
|
+
# `eth_accounts` or external signer if no sender key is provided.
|
221
186
|
#
|
222
187
|
# See {#deploy} for params and overloads.
|
223
188
|
#
|
@@ -228,7 +193,7 @@ module Eth
|
|
228
193
|
contract.address = Address.new(addr).to_s
|
229
194
|
end
|
230
195
|
|
231
|
-
# Deploys a contract. Uses `
|
196
|
+
# Deploys a contract. Uses `eth_accounts` or external signer
|
232
197
|
# if no sender key is provided.
|
233
198
|
#
|
234
199
|
# **Note**, that many remote providers (e.g., Infura) do not provide
|
@@ -266,37 +231,7 @@ module Eth
|
|
266
231
|
chain_id: chain_id,
|
267
232
|
data: data,
|
268
233
|
}
|
269
|
-
|
270
|
-
params.merge!({
|
271
|
-
gas_price: max_fee_per_gas,
|
272
|
-
})
|
273
|
-
else
|
274
|
-
params.merge!({
|
275
|
-
priority_fee: max_priority_fee_per_gas,
|
276
|
-
max_gas_fee: max_fee_per_gas,
|
277
|
-
})
|
278
|
-
end
|
279
|
-
unless kwargs[:sender_key].nil?
|
280
|
-
# Uses the provided key as sender and signer
|
281
|
-
params.merge!({
|
282
|
-
from: kwargs[:sender_key].address,
|
283
|
-
nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
|
284
|
-
})
|
285
|
-
tx = Eth::Tx.new(params)
|
286
|
-
tx.sign kwargs[:sender_key]
|
287
|
-
return eth_send_raw_transaction(tx.hex)["result"]
|
288
|
-
else
|
289
|
-
|
290
|
-
# Does not allow accessing accounts on remote connections
|
291
|
-
raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
|
292
|
-
|
293
|
-
# Uses the default account as sender and external signer
|
294
|
-
params.merge!({
|
295
|
-
from: default_account,
|
296
|
-
nonce: kwargs[:nonce] || get_nonce(default_account),
|
297
|
-
})
|
298
|
-
return eth_send_transaction(params)["result"]
|
299
|
-
end
|
234
|
+
send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
|
300
235
|
end
|
301
236
|
|
302
237
|
# Calls a contract function without executing it
|
@@ -315,7 +250,6 @@ module Eth
|
|
315
250
|
# @param *args optional function arguments.
|
316
251
|
# @param **sender_key [Eth::Key] the sender private key.
|
317
252
|
# @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
318
|
-
# @param **gas_limit [Integer] optional gas limit override for deploying the contract.
|
319
253
|
# @return [Object] returns the result of the call.
|
320
254
|
def call(contract, function, *args, **kwargs)
|
321
255
|
func = contract.functions.select { |func| func.name == function }
|
@@ -328,9 +262,9 @@ module Eth
|
|
328
262
|
end
|
329
263
|
output = call_raw(contract, selected_func, *args, **kwargs)
|
330
264
|
if output&.length == 1
|
331
|
-
|
265
|
+
output[0]
|
332
266
|
else
|
333
|
-
|
267
|
+
output
|
334
268
|
end
|
335
269
|
end
|
336
270
|
|
@@ -354,7 +288,7 @@ module Eth
|
|
354
288
|
# @param **sender_key [Eth::Key] the sender private key.
|
355
289
|
# @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
356
290
|
# @param **address [Eth::Address] contract address.
|
357
|
-
# @param **gas_limit [Integer] optional gas limit override for
|
291
|
+
# @param **gas_limit [Integer] optional gas limit override for transacting with the contract.
|
358
292
|
# @param **nonce [Integer] optional specific nonce for transaction.
|
359
293
|
# @param **tx_value [Integer] optional transaction value field filling.
|
360
294
|
# @return [Object] returns the result of the transaction.
|
@@ -362,7 +296,7 @@ module Eth
|
|
362
296
|
gas_limit = if kwargs[:gas_limit]
|
363
297
|
kwargs[:gas_limit]
|
364
298
|
else
|
365
|
-
Tx.estimate_intrinsic_gas(contract.bin)
|
299
|
+
Tx.estimate_intrinsic_gas(contract.bin)
|
366
300
|
end
|
367
301
|
fun = contract.functions.select { |func| func.name == function }[0]
|
368
302
|
params = {
|
@@ -372,37 +306,7 @@ module Eth
|
|
372
306
|
to: kwargs[:address] || contract.address,
|
373
307
|
data: call_payload(fun, args),
|
374
308
|
}
|
375
|
-
|
376
|
-
params.merge!({
|
377
|
-
gas_price: max_fee_per_gas,
|
378
|
-
})
|
379
|
-
else
|
380
|
-
params.merge!({
|
381
|
-
priority_fee: max_priority_fee_per_gas,
|
382
|
-
max_gas_fee: max_fee_per_gas,
|
383
|
-
})
|
384
|
-
end
|
385
|
-
unless kwargs[:sender_key].nil?
|
386
|
-
# use the provided key as sender and signer
|
387
|
-
params.merge!({
|
388
|
-
from: kwargs[:sender_key].address,
|
389
|
-
nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
|
390
|
-
})
|
391
|
-
tx = Eth::Tx.new(params)
|
392
|
-
tx.sign kwargs[:sender_key]
|
393
|
-
return eth_send_raw_transaction(tx.hex)["result"]
|
394
|
-
else
|
395
|
-
|
396
|
-
# do not allow accessing accounts on remote connections
|
397
|
-
raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
|
398
|
-
|
399
|
-
# use the default account as sender and external signer
|
400
|
-
params.merge!({
|
401
|
-
from: default_account,
|
402
|
-
nonce: kwargs[:nonce] || get_nonce(default_account),
|
403
|
-
})
|
404
|
-
return eth_send_transaction(params)["result"]
|
405
|
-
end
|
309
|
+
send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
|
406
310
|
end
|
407
311
|
|
408
312
|
# Executes a contract function with a transaction and waits for it
|
@@ -437,7 +341,7 @@ module Eth
|
|
437
341
|
signature = Util.hex_to_bin signature if Util.hex? signature
|
438
342
|
magic = Util.hex_to_bin magic if Util.hex? magic
|
439
343
|
result = call(contract, "isValidSignature", hash, signature)
|
440
|
-
|
344
|
+
result === magic
|
441
345
|
end
|
442
346
|
|
443
347
|
# Gives control over resetting the RPC request ID back to zero.
|
@@ -496,11 +400,45 @@ module Eth
|
|
496
400
|
# Allows to determine if we work with a local connectoin
|
497
401
|
def local?
|
498
402
|
if self.instance_of? Eth::Client::Ipc
|
499
|
-
|
403
|
+
true
|
500
404
|
elsif self.host === "127.0.0.1" || self.host === "localhost"
|
501
|
-
|
405
|
+
true
|
502
406
|
else
|
503
|
-
|
407
|
+
false
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# Prepares a transaction to be send for the given params.
|
412
|
+
def send_transaction(params, legacy, key, nonce)
|
413
|
+
if legacy
|
414
|
+
params.merge!({ gas_price: max_fee_per_gas })
|
415
|
+
else
|
416
|
+
params.merge!({
|
417
|
+
priority_fee: max_priority_fee_per_gas,
|
418
|
+
max_gas_fee: max_fee_per_gas,
|
419
|
+
})
|
420
|
+
end
|
421
|
+
unless key.nil?
|
422
|
+
|
423
|
+
# use the provided key as sender and signer
|
424
|
+
params.merge!({
|
425
|
+
from: key.address,
|
426
|
+
nonce: nonce || get_nonce(key.address),
|
427
|
+
})
|
428
|
+
tx = Eth::Tx.new(params)
|
429
|
+
tx.sign key
|
430
|
+
eth_send_raw_transaction(tx.hex)["result"]
|
431
|
+
else
|
432
|
+
|
433
|
+
# do not allow accessing accounts on remote connections
|
434
|
+
raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
|
435
|
+
|
436
|
+
# use the default account as sender and external signer
|
437
|
+
params.merge!({
|
438
|
+
from: default_account,
|
439
|
+
nonce: nonce || get_nonce(default_account),
|
440
|
+
})
|
441
|
+
eth_send_transaction(params)["result"]
|
504
442
|
end
|
505
443
|
end
|
506
444
|
|
@@ -534,16 +472,17 @@ module Eth
|
|
534
472
|
|
535
473
|
# Prepares parameters and sends the command to the client.
|
536
474
|
def send_command(command, args)
|
537
|
-
|
475
|
+
@block_number ||= "latest"
|
476
|
+
args << block_number if ["eth_getBalance", "eth_call"].include? command
|
538
477
|
payload = {
|
539
478
|
jsonrpc: "2.0",
|
540
479
|
method: command,
|
541
480
|
params: marshal(args),
|
542
481
|
id: next_id,
|
543
482
|
}
|
544
|
-
output = JSON.parse(
|
483
|
+
output = JSON.parse(send_request(payload.to_json))
|
545
484
|
raise IOError, output["error"]["message"] unless output["error"].nil?
|
546
|
-
|
485
|
+
output
|
547
486
|
end
|
548
487
|
|
549
488
|
# Increments the request id.
|
@@ -564,18 +503,18 @@ module Eth
|
|
564
503
|
def marshal(params)
|
565
504
|
params = params.dup
|
566
505
|
if params.is_a? Array
|
567
|
-
|
506
|
+
params.map! { |param| marshal(param) }
|
568
507
|
elsif params.is_a? Hash
|
569
508
|
params = camelize!(params)
|
570
|
-
|
509
|
+
params.transform_values! { |param| marshal(param) }
|
571
510
|
elsif params.is_a? Numeric
|
572
|
-
|
511
|
+
Util.prefix_hex "#{params.to_i.to_s(16)}"
|
573
512
|
elsif params.is_a? Address
|
574
|
-
|
513
|
+
params.to_s
|
575
514
|
elsif Util.hex? params
|
576
|
-
|
515
|
+
Util.prefix_hex params
|
577
516
|
else
|
578
|
-
|
517
|
+
params
|
579
518
|
end
|
580
519
|
end
|
581
520
|
end
|
@@ -583,5 +522,4 @@ end
|
|
583
522
|
|
584
523
|
# Load the client/* libraries
|
585
524
|
require "eth/client/http"
|
586
|
-
require "eth/client/http_auth"
|
587
525
|
require "eth/client/ipc"
|
data/lib/eth/contract/event.rb
CHANGED
@@ -26,9 +26,11 @@ module Eth
|
|
26
26
|
# @param data [Hash] contract event data.
|
27
27
|
def initialize(data)
|
28
28
|
@name = data["name"]
|
29
|
-
@input_types = data["inputs"].collect
|
29
|
+
@input_types = data["inputs"].collect do |x|
|
30
|
+
type_name x
|
31
|
+
end
|
30
32
|
@inputs = data["inputs"].collect { |x| x["name"] }
|
31
|
-
@event_string =
|
33
|
+
@event_string = Abi::Event.signature(data)
|
32
34
|
@signature = Digest::Keccak.hexdigest(@event_string, 256)
|
33
35
|
end
|
34
36
|
|
@@ -38,5 +40,17 @@ module Eth
|
|
38
40
|
def set_address(address)
|
39
41
|
@address = address.nil? ? nil : Eth::Address.new(address).address
|
40
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def type_name(x)
|
47
|
+
type = x["type"]
|
48
|
+
case type
|
49
|
+
when "tuple"
|
50
|
+
"(#{x["components"].collect { |c| type_name(c) }.join(",")})"
|
51
|
+
else
|
52
|
+
type
|
53
|
+
end
|
54
|
+
end
|
41
55
|
end
|
42
56
|
end
|
data/lib/eth/contract.rb
CHANGED
@@ -14,6 +14,8 @@
|
|
14
14
|
|
15
15
|
# -*- encoding : ascii-8bit -*-
|
16
16
|
|
17
|
+
require "forwardable"
|
18
|
+
|
17
19
|
# Provides the {Eth} module.
|
18
20
|
module Eth
|
19
21
|
|
@@ -27,11 +29,19 @@ module Eth
|
|
27
29
|
|
28
30
|
# Constructor of the {Eth::Contract} class.
|
29
31
|
#
|
32
|
+
# **Note**, do not use this directly. Use
|
33
|
+
# {from_abi}, {from_bin}, or {from_file}!
|
34
|
+
#
|
30
35
|
# @param name [String] contract name.
|
31
36
|
# @param bin [String] contract bin string.
|
32
37
|
# @param abi [String] contract abi string.
|
33
38
|
def initialize(name, bin, abi)
|
34
|
-
|
39
|
+
|
40
|
+
# The contract name will be the class name and needs title casing.
|
41
|
+
_name = name.dup
|
42
|
+
_name[0] = name[0].upcase
|
43
|
+
|
44
|
+
@name = _name
|
35
45
|
@bin = bin
|
36
46
|
@abi = abi
|
37
47
|
@constructor_inputs, @functions, @events = parse_abi(abi)
|
data/lib/eth/eip712.rb
CHANGED
@@ -114,9 +114,13 @@ module Eth
|
|
114
114
|
value = data[field[:name].to_sym]
|
115
115
|
type = field[:type]
|
116
116
|
raise NotImplementedError, "Arrays currently unimplemented for EIP-712." if type.end_with? "]"
|
117
|
-
if type == "string"
|
117
|
+
if type == "string"
|
118
118
|
encoded_types.push "bytes32"
|
119
119
|
encoded_values.push Util.keccak256 value
|
120
|
+
elsif type == "bytes"
|
121
|
+
encoded_types.push "bytes32"
|
122
|
+
value = Util.hex_to_bin value
|
123
|
+
encoded_values.push Util.keccak256 value
|
120
124
|
elsif !types[type.to_sym].nil?
|
121
125
|
encoded_types.push "bytes32"
|
122
126
|
value = encode_data type, value, types
|
data/lib/eth/solidity.rb
CHANGED
@@ -28,10 +28,12 @@ module Eth
|
|
28
28
|
|
29
29
|
# Instantiates a Solidity `solc` system compiler binding that can be
|
30
30
|
# used to compile Solidity contracts.
|
31
|
-
|
31
|
+
#
|
32
|
+
# @param path [String] optional override of the solidity compiler path.
|
33
|
+
def initialize(path = nil)
|
32
34
|
|
33
|
-
# Currently only supports `solc`.
|
34
|
-
solc = get_compiler_path
|
35
|
+
# Currently only supports `solc`. Try to override with `path`.
|
36
|
+
solc = path || get_compiler_path
|
35
37
|
raise SystemCallError, "Unable to find the solc compiler path!" if solc.nil?
|
36
38
|
@compiler = solc
|
37
39
|
end
|
@@ -43,9 +45,10 @@ module Eth
|
|
43
45
|
def compile(contract)
|
44
46
|
raise Errno::ENOENT, "Contract file not found: #{contract}" unless File.exist? contract
|
45
47
|
flag_opt = "--optimize"
|
48
|
+
flag_ir = "--via-ir"
|
46
49
|
flag_json = "--combined-json=bin,abi"
|
47
50
|
path = File.realpath contract
|
48
|
-
output, error, status = Open3.capture3 @compiler, flag_opt, flag_json, path
|
51
|
+
output, error, status = Open3.capture3 @compiler, flag_opt, flag_ir, flag_json, path
|
49
52
|
raise SystemCallError, "Unable to run solc compiler!" if status.exitstatus === 127
|
50
53
|
raise CompilerError, error unless status.success?
|
51
54
|
json = JSON.parse output
|
data/lib/eth/tx/eip1559.rb
CHANGED
@@ -186,11 +186,16 @@ module Eth
|
|
186
186
|
# last but not least, set the type.
|
187
187
|
@type = TYPE_1559
|
188
188
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
189
|
+
unless recovery_id.nil?
|
190
|
+
# recover sender address
|
191
|
+
v = Chain.to_v recovery_id, chain_id
|
192
|
+
public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id)
|
193
|
+
address = Util.public_key_to_address(public_key).to_s
|
194
|
+
@sender = Tx.sanitize_address address
|
195
|
+
else
|
196
|
+
# keep the 'from' field blank
|
197
|
+
@sender = Tx.sanitize_address nil
|
198
|
+
end
|
194
199
|
end
|
195
200
|
|
196
201
|
# Creates an unsigned copy of a transaction payload.
|
data/lib/eth/tx/eip2930.rb
CHANGED
@@ -181,11 +181,16 @@ module Eth
|
|
181
181
|
# last but not least, set the type.
|
182
182
|
@type = TYPE_2930
|
183
183
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
184
|
+
unless recovery_id.nil?
|
185
|
+
# recover sender address
|
186
|
+
v = Chain.to_v recovery_id, chain_id
|
187
|
+
public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id)
|
188
|
+
address = Util.public_key_to_address(public_key).to_s
|
189
|
+
@sender = Tx.sanitize_address address
|
190
|
+
else
|
191
|
+
# keep the 'from' field blank
|
192
|
+
@sender = Tx.sanitize_address nil
|
193
|
+
end
|
189
194
|
end
|
190
195
|
|
191
196
|
# Creates an unsigned copy of a transaction payload.
|
data/lib/eth/tx/legacy.rb
CHANGED
@@ -154,13 +154,11 @@ module Eth
|
|
154
154
|
_set_signature(v, r, s)
|
155
155
|
|
156
156
|
unless chain_id.nil?
|
157
|
-
|
158
157
|
# recover sender address
|
159
158
|
public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v}", chain_id)
|
160
159
|
address = Util.public_key_to_address(public_key).to_s
|
161
160
|
@sender = Tx.sanitize_address address
|
162
161
|
else
|
163
|
-
|
164
162
|
# keep the 'from' field blank
|
165
163
|
@sender = Tx.sanitize_address nil
|
166
164
|
end
|
data/lib/eth/tx.rb
CHANGED
@@ -51,6 +51,9 @@ module Eth
|
|
51
51
|
# The calldata gas cost of a zero byte.
|
52
52
|
COST_ZERO_BYTE = 4.freeze
|
53
53
|
|
54
|
+
# The initcode gas cost for each word (32 bytes).
|
55
|
+
COST_INITCODE_WORD = 2.freeze
|
56
|
+
|
54
57
|
# The access list gas cost of a storage key as per EIP-2930.
|
55
58
|
COST_STORAGE_KEY = 1_900.freeze
|
56
59
|
|
@@ -156,7 +159,7 @@ module Eth
|
|
156
159
|
end
|
157
160
|
|
158
161
|
# Estimates intrinsic gas for provided call data (EIP-2028) and
|
159
|
-
# access lists (EIP-2930).
|
162
|
+
# access lists (EIP-2930). Respects initcode word cost (EIP-3860).
|
160
163
|
#
|
161
164
|
# @param data [String] the call data.
|
162
165
|
# @param list [Array] the access list.
|
@@ -173,6 +176,10 @@ module Eth
|
|
173
176
|
# count non-zero bytes
|
174
177
|
none = data.size - zero
|
175
178
|
gas += none * COST_NON_ZERO_BYTE
|
179
|
+
|
180
|
+
# count "words" as per EIP-3860
|
181
|
+
word_count = (data.length.to_f / 32.0).ceil
|
182
|
+
gas += word_count * COST_INITCODE_WORD
|
176
183
|
end
|
177
184
|
unless list.nil? or list.empty?
|
178
185
|
list.each do |entry|
|
@@ -187,7 +194,7 @@ module Eth
|
|
187
194
|
end
|
188
195
|
end
|
189
196
|
end
|
190
|
-
return gas
|
197
|
+
return gas.to_i
|
191
198
|
end
|
192
199
|
|
193
200
|
# Validates the common transaction fields such as nonce, gas limit,
|
data/lib/eth/util.rb
CHANGED
@@ -28,6 +28,7 @@ module Eth
|
|
28
28
|
# @return [Eth::Address] an Ethereum address.
|
29
29
|
def public_key_to_address(str)
|
30
30
|
str = hex_to_bin str if hex? str
|
31
|
+
str = Secp256k1::PublicKey.from_data(str).uncompressed
|
31
32
|
bytes = keccak256(str[1..-1])[-20..-1]
|
32
33
|
Address.new bin_to_prefixed_hex bytes
|
33
34
|
end
|
data/lib/eth/version.rb
CHANGED
@@ -15,6 +15,15 @@
|
|
15
15
|
# Provides the {Eth} module.
|
16
16
|
module Eth
|
17
17
|
|
18
|
-
# Defines the version of the {Eth} module.
|
19
|
-
|
18
|
+
# Defines the major version of the {Eth} module.
|
19
|
+
MAJOR = 0.freeze
|
20
|
+
|
21
|
+
# Defines the minor version of the {Eth} module.
|
22
|
+
MINOR = 5.freeze
|
23
|
+
|
24
|
+
# Defines the patch version of the {Eth} module.
|
25
|
+
PATCH = 12.freeze
|
26
|
+
|
27
|
+
# Defines the version string of the {Eth} module.
|
28
|
+
VERSION = [MAJOR, MINOR, PATCH].join(".").freeze
|
20
29
|
end
|