eth 0.5.10 → 0.5.12

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