eth 0.5.10 → 0.5.12

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.
@@ -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} intead.
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
- @uri = URI("#{uri.scheme}://#{@host}:#{@port}#{uri.path}")
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 send(payload)
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
@@ -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} intead.
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 send(payload)
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 coinbase.
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 default gas limit for the transaction, defaults to {Tx::DEFAULT_GAS_LIMIT}.
38
- attr_accessor :gas_limit
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}, and {Tx::DEFAULT_GAS_LIMIT}. Use
48
- # {#max_priority_fee_per_gas}, {#max_fee_per_gas}, and {#gas_limit} to set
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 (coinbase) of the connected client.
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 coinbase account address.
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 eth_coinbase["result"]
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 `eth_coinbase` and external signer if no sender key is
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 `eth_coinbase` and external signer
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: gas_limit,
145
+ gas_limit: Tx::DEFAULT_GAS_LIMIT,
150
146
  chain_id: chain_id,
151
147
  }
152
- if kwargs[:legacy]
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 deploying the contract.
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
- # `eth_coinbase` or external signer if no sender key is provided.
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 `eth_coinbase` or external signer
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
- if kwargs[:legacy]
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
- return output[0]
265
+ output[0]
332
266
  else
333
- return output
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 deploying the contract.
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) + Tx::CREATE_GAS
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
- if kwargs[:legacy]
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
- return result === magic
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
- return true
403
+ true
500
404
  elsif self.host === "127.0.0.1" || self.host === "localhost"
501
- return true
405
+ true
502
406
  else
503
- return false
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
- args << "latest" if ["eth_getBalance", "eth_call"].include? command
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(send(payload.to_json))
483
+ output = JSON.parse(send_request(payload.to_json))
545
484
  raise IOError, output["error"]["message"] unless output["error"].nil?
546
- return output
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
- return params.map! { |param| marshal(param) }
506
+ params.map! { |param| marshal(param) }
568
507
  elsif params.is_a? Hash
569
508
  params = camelize!(params)
570
- return params.transform_values! { |param| marshal(param) }
509
+ params.transform_values! { |param| marshal(param) }
571
510
  elsif params.is_a? Numeric
572
- return Util.prefix_hex "#{params.to_i.to_s(16)}"
511
+ Util.prefix_hex "#{params.to_i.to_s(16)}"
573
512
  elsif params.is_a? Address
574
- return params.to_s
513
+ params.to_s
575
514
  elsif Util.hex? params
576
- return Util.prefix_hex params
515
+ Util.prefix_hex params
577
516
  else
578
- return params
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"
@@ -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 { |x| x["type"] }
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 = "#{@name}(#{@input_types.join(",")})"
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
- @name = name
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" or type == "bytes"
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
- def initialize
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
@@ -186,11 +186,16 @@ module Eth
186
186
  # last but not least, set the type.
187
187
  @type = TYPE_1559
188
188
 
189
- # recover sender address
190
- v = Chain.to_v recovery_id, chain_id
191
- public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id)
192
- address = Util.public_key_to_address(public_key).to_s
193
- @sender = Tx.sanitize_address address
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.
@@ -181,11 +181,16 @@ module Eth
181
181
  # last but not least, set the type.
182
182
  @type = TYPE_2930
183
183
 
184
- # recover sender address
185
- v = Chain.to_v recovery_id, chain_id
186
- public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id)
187
- address = Util.public_key_to_address(public_key).to_s
188
- @sender = Tx.sanitize_address address
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
- VERSION = "0.5.10".freeze
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