eth 0.5.8 → 0.5.10

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +2 -1
  3. data/CHANGELOG.md +32 -0
  4. data/CODE_OF_CONDUCT.md +122 -0
  5. data/CONTRIBUTING.md +61 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +6 -2
  8. data/SECURITY.md +24 -0
  9. data/abi/ens_registry.json +436 -0
  10. data/abi/ens_resolver.json +885 -0
  11. data/lib/eth/abi/event.rb +1 -1
  12. data/lib/eth/abi/type.rb +46 -12
  13. data/lib/eth/abi.rb +72 -14
  14. data/lib/eth/address.rb +2 -2
  15. data/lib/eth/api.rb +1 -1
  16. data/lib/eth/chain.rb +19 -7
  17. data/lib/eth/client/http.rb +1 -1
  18. data/lib/eth/client/http_auth.rb +1 -1
  19. data/lib/eth/client/ipc.rb +1 -1
  20. data/lib/eth/client.rb +118 -16
  21. data/lib/eth/constant.rb +1 -1
  22. data/lib/eth/contract/event.rb +1 -1
  23. data/lib/eth/contract/function.rb +2 -2
  24. data/lib/eth/contract/function_input.rb +7 -2
  25. data/lib/eth/contract/function_output.rb +1 -1
  26. data/lib/eth/contract/initializer.rb +1 -1
  27. data/lib/eth/contract.rb +1 -1
  28. data/lib/eth/eip712.rb +2 -2
  29. data/lib/eth/ens/coin_type.rb +50 -0
  30. data/lib/eth/ens/resolver.rb +68 -24
  31. data/lib/eth/ens.rb +28 -0
  32. data/lib/eth/key/decrypter.rb +1 -1
  33. data/lib/eth/key/encrypter.rb +1 -1
  34. data/lib/eth/key.rb +5 -5
  35. data/lib/eth/rlp/decoder.rb +2 -2
  36. data/lib/eth/rlp/encoder.rb +3 -3
  37. data/lib/eth/rlp/sedes/big_endian_int.rb +1 -1
  38. data/lib/eth/rlp/sedes/binary.rb +2 -2
  39. data/lib/eth/rlp/sedes/list.rb +5 -5
  40. data/lib/eth/rlp/sedes.rb +4 -4
  41. data/lib/eth/rlp.rb +1 -1
  42. data/lib/eth/signature.rb +4 -4
  43. data/lib/eth/solidity.rb +5 -3
  44. data/lib/eth/tx/eip1559.rb +3 -3
  45. data/lib/eth/tx/eip2930.rb +3 -3
  46. data/lib/eth/tx/legacy.rb +3 -3
  47. data/lib/eth/tx.rb +5 -5
  48. data/lib/eth/unit.rb +1 -1
  49. data/lib/eth/util.rb +14 -14
  50. data/lib/eth/version.rb +2 -2
  51. data/lib/eth.rb +2 -2
  52. metadata +9 -3
  53. data/abis/ens.json +0 -422
data/lib/eth/client.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -37,6 +37,9 @@ module Eth
37
37
  # The default gas limit for the transaction, defaults to {Tx::DEFAULT_GAS_LIMIT}.
38
38
  attr_accessor :gas_limit
39
39
 
40
+ # A custom error type if a contract interaction fails.
41
+ class ContractExecutionError < StandardError; end
42
+
40
43
  # Creates a new RPC-Client, either by providing an HTTP/S host or
41
44
  # an IPC path. Supports basic authentication with username and password.
42
45
  #
@@ -73,6 +76,7 @@ module Eth
73
76
  #
74
77
  # @return [Eth::Address] the coinbase account address.
75
78
  def default_account
79
+ raise ArgumentError, "The default account is not available on remote connections!" unless local?
76
80
  @default_account ||= Address.new eth_coinbase["result"]
77
81
  end
78
82
 
@@ -99,6 +103,17 @@ module Eth
99
103
  eth_get_transaction_count(address, "pending")["result"].to_i 16
100
104
  end
101
105
 
106
+ # Resolves an ENS name to an Ethereum address on the connected chain.
107
+ #
108
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
109
+ # @param registry [String] the address for the ENS registry.
110
+ # @param coin_type [Integer] the coin type as per EIP-2304.
111
+ # @return [Eth::Address] the Ethereum address resolved from the ENS record.
112
+ def resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::CoinType::ETHEREUM)
113
+ ens = Ens::Resolver.new(self, registry)
114
+ ens.resolve(ens_name, coin_type)
115
+ end
116
+
102
117
  # Simply transfer Ether to an account and waits for it to be mined.
103
118
  # Uses `eth_coinbase` and external signer if no sender key is
104
119
  # provided.
@@ -115,7 +130,7 @@ module Eth
115
130
  # if no sender key is provided.
116
131
  #
117
132
  # **Note**, that many remote providers (e.g., Infura) do not provide
118
- # any accounts. Provide a `sender_key` if you experience issues.
133
+ # any accounts. Provide a `sender_key:` if you experience issues.
119
134
  #
120
135
  # @overload transfer(destination, amount)
121
136
  # @param destination [Eth::Address] the destination address.
@@ -156,6 +171,9 @@ module Eth
156
171
  return eth_send_raw_transaction(tx.hex)["result"]
157
172
  else
158
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
+
159
177
  # use the default account as sender and external signer
160
178
  params.merge!({
161
179
  from: default_account,
@@ -165,6 +183,39 @@ module Eth
165
183
  end
166
184
  end
167
185
 
186
+ # Transfers a token that implements the ERC20 `transfer()` interface.
187
+ #
188
+ # See {#transfer_erc20} for params and overloads.
189
+ #
190
+ # @return [Object] returns the result of the transaction.
191
+ def transfer_erc20_and_wait(erc20_contract, destination, amount, **kwargs)
192
+ transact_and_wait(erc20_contract, "transfer", destination, amount, **kwargs)
193
+ end
194
+
195
+ # Transfers a token that implements the ERC20 `transfer()` interface.
196
+ #
197
+ # **Note**, that many remote providers (e.g., Infura) do not provide
198
+ # any accounts. Provide a `sender_key:` if you experience issues.
199
+ #
200
+ # @overload transfer_erc20(erc20_contract, destination, amount)
201
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
202
+ # @param destination [Eth::Address] the destination address.
203
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
204
+ # @overload transfer_erc20(erc20_contract, destination, amount, **kwargs)
205
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
206
+ # @param destination [Eth::Address] the destination address.
207
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
208
+ # @param **sender_key [Eth::Key] the sender private key.
209
+ # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
210
+ # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
211
+ # @param **nonce [Integer] optional specific nonce for transaction.
212
+ # @param **tx_value [Integer] optional transaction value field filling.
213
+ # @return [Object] returns the result of the transaction.
214
+ def transfer_erc20(erc20_contract, destination, amount, **kwargs)
215
+ destination = destination.to_s if destination.instance_of? Eth::Address
216
+ transact(erc20_contract, "transfer", destination, amount, **kwargs)
217
+ end
218
+
168
219
  # Deploys a contract and waits for it to be mined. Uses
169
220
  # `eth_coinbase` or external signer if no sender key is provided.
170
221
  #
@@ -181,7 +232,7 @@ module Eth
181
232
  # if no sender key is provided.
182
233
  #
183
234
  # **Note**, that many remote providers (e.g., Infura) do not provide
184
- # any accounts. Provide a `sender_key` if you experience issues.
235
+ # any accounts. Provide a `sender_key:` if you experience issues.
185
236
  #
186
237
  # @overload deploy(contract)
187
238
  # @param contract [Eth::Contract] contracts to deploy.
@@ -235,6 +286,10 @@ module Eth
235
286
  tx.sign kwargs[:sender_key]
236
287
  return eth_send_raw_transaction(tx.hex)["result"]
237
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
+
238
293
  # Uses the default account as sender and external signer
239
294
  params.merge!({
240
295
  from: default_account,
@@ -263,9 +318,15 @@ module Eth
263
318
  # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
264
319
  # @return [Object] returns the result of the call.
265
320
  def call(contract, function, *args, **kwargs)
266
- func = contract.functions.select { |func| func.name == function }[0]
267
- raise ArgumentError, "this function does not exist!" if func.nil?
268
- output = call_raw(contract, func, *args, **kwargs)
321
+ func = contract.functions.select { |func| func.name == function }
322
+ raise ArgumentError, "this function does not exist!" if func.nil? || func.size === 0
323
+ selected_func = func.first
324
+ func.each do |f|
325
+ if f.inputs.size === args.size
326
+ selected_func = f
327
+ end
328
+ end
329
+ output = call_raw(contract, selected_func, *args, **kwargs)
269
330
  if output&.length == 1
270
331
  return output[0]
271
332
  else
@@ -277,7 +338,7 @@ module Eth
277
338
  # contract read/write).
278
339
  #
279
340
  # **Note**, that many remote providers (e.g., Infura) do not provide
280
- # any accounts. Provide a `sender_key` if you experience issues.
341
+ # any accounts. Provide a `sender_key:` if you experience issues.
281
342
  #
282
343
  # @overload transact(contract, function)
283
344
  # @param contract [Eth::Contract] the subject contract to write to.
@@ -331,6 +392,10 @@ module Eth
331
392
  tx.sign kwargs[:sender_key]
332
393
  return eth_send_raw_transaction(tx.hex)["result"]
333
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
+
334
399
  # use the default account as sender and external signer
335
400
  params.merge!({
336
401
  from: default_account,
@@ -345,9 +410,15 @@ module Eth
345
410
  #
346
411
  # See {#transact} for params and overloads.
347
412
  #
413
+ # @raise [Client::ContractExecutionError] if the execution fails.
348
414
  # @return [Object] returns the result of the transaction.
349
415
  def transact_and_wait(contract, function, *args, **kwargs)
350
- wait_for_tx(transact(contract, function, *args, **kwargs))
416
+ begin
417
+ hash = wait_for_tx(transact(contract, function, *args, **kwargs))
418
+ return hash if tx_succeeded? hash
419
+ rescue IOError => e
420
+ raise ContractExecutionError, e
421
+ end
351
422
  end
352
423
 
353
424
  # Provides an interface to call `isValidSignature` as per EIP-1271 on a given
@@ -362,9 +433,9 @@ module Eth
362
433
  # @raise [ArgumentError] in case the contract cannot be called yet.
363
434
  def is_valid_signature(contract, hash, signature, magic = "1626ba7e")
364
435
  raise ArgumentError, "Contract not deployed yet." if contract.address.nil?
365
- hash = Util.hex_to_bin hash if Util.is_hex? hash
366
- signature = Util.hex_to_bin signature if Util.is_hex? signature
367
- magic = Util.hex_to_bin magic if Util.is_hex? magic
436
+ hash = Util.hex_to_bin hash if Util.hex? hash
437
+ signature = Util.hex_to_bin signature if Util.hex? signature
438
+ magic = Util.hex_to_bin magic if Util.hex? magic
368
439
  result = call(contract, "isValidSignature", hash, signature)
369
440
  return result === magic
370
441
  end
@@ -377,15 +448,24 @@ module Eth
377
448
  @id = 0
378
449
  end
379
450
 
380
- # Checkes wether a transaction is mined or not.
451
+ # Checks whether a transaction is mined or not.
381
452
  #
382
453
  # @param hash [String] the transaction hash.
383
454
  # @return [Boolean] true if included in a block.
384
- def is_mined_tx?(hash)
455
+ def tx_mined?(hash)
385
456
  mined_tx = eth_get_transaction_by_hash hash
386
457
  !mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
387
458
  end
388
459
 
460
+ # Checks whether a contract transaction succeeded or not.
461
+ #
462
+ # @param hash [String] the transaction hash.
463
+ # @return [Boolean] true if status is success.
464
+ def tx_succeeded?(hash)
465
+ tx_receipt = eth_get_transaction_receipt(hash)
466
+ !tx_receipt.nil? && !tx_receipt["result"].nil? && tx_receipt["result"]["status"] == "0x1"
467
+ end
468
+
389
469
  # Waits for an transaction to be mined by the connected chain.
390
470
  #
391
471
  # @param hash [String] the transaction hash.
@@ -397,7 +477,7 @@ module Eth
397
477
  retry_rate = 0.1
398
478
  loop do
399
479
  raise Timeout::Error if ((Time.now - start_time) > timeout)
400
- return hash if is_mined_tx? hash
480
+ return hash if tx_mined? hash
401
481
  sleep retry_rate
402
482
  end
403
483
  end
@@ -413,6 +493,17 @@ module Eth
413
493
 
414
494
  private
415
495
 
496
+ # Allows to determine if we work with a local connectoin
497
+ def local?
498
+ if self.instance_of? Eth::Client::Ipc
499
+ return true
500
+ elsif self.host === "127.0.0.1" || self.host === "localhost"
501
+ return true
502
+ else
503
+ return false
504
+ end
505
+ end
506
+
416
507
  # Non-transactional function call called from call().
417
508
  # @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
418
509
  def call_raw(contract, func, *args, **kwargs)
@@ -430,7 +521,7 @@ module Eth
430
521
 
431
522
  # Encodes function call payloads.
432
523
  def call_payload(fun, args)
433
- types = fun.inputs.map { |i| i.type }
524
+ types = fun.inputs.map(&:parsed_type)
434
525
  encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
435
526
  Util.prefix_hex(fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str))
436
527
  end
@@ -460,17 +551,28 @@ module Eth
460
551
  @id += 1
461
552
  end
462
553
 
554
+ # expects Hash object
555
+ def camelize!(params)
556
+ params.transform_keys! do |k|
557
+ k = k.to_s.split(/_/).map(&:capitalize).join
558
+ k[0] = k[0].downcase
559
+ k.to_sym
560
+ end
561
+ end
562
+
463
563
  # Recursively marshals all request parameters.
464
564
  def marshal(params)
565
+ params = params.dup
465
566
  if params.is_a? Array
466
567
  return params.map! { |param| marshal(param) }
467
568
  elsif params.is_a? Hash
569
+ params = camelize!(params)
468
570
  return params.transform_values! { |param| marshal(param) }
469
571
  elsif params.is_a? Numeric
470
572
  return Util.prefix_hex "#{params.to_i.to_s(16)}"
471
573
  elsif params.is_a? Address
472
574
  return params.to_s
473
- elsif Util.is_hex? params
575
+ elsif Util.hex? params
474
576
  return Util.prefix_hex params
475
577
  else
476
578
  return params
data/lib/eth/constant.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ module Eth
43
43
  # @param inputs [Array<Eth::Contract::FunctionInput>] function input class list.
44
44
  # @return [String] function string.
45
45
  def self.calc_signature(name, inputs)
46
- "#{name}(#{inputs.collect { |x| x.raw_type }.join(",")})"
46
+ "#{name}(#{inputs.map { |x| x.parsed_type.to_s }.join(",")})"
47
47
  end
48
48
 
49
49
  # Encodes a function signature.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ module Eth
26
26
  # @param data [Hash] contract abi data.
27
27
  def initialize(data)
28
28
  @raw_type = data["type"]
29
- @type = Eth::Abi::Type.parse(data["type"])
29
+ @type = Eth::Abi::Type.parse(data["type"], data["components"])
30
30
  @name = data["name"]
31
31
  end
32
32
 
@@ -34,5 +34,10 @@ module Eth
34
34
  def type
35
35
  @type.base_type + @type.sub_type + @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
36
36
  end
37
+
38
+ # Returns parsed types.
39
+ def parsed_type
40
+ @type
41
+ end
37
42
  end
38
43
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/contract.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/eip712.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -149,7 +149,7 @@ module Eth
149
149
  # @return [Array] the data in the data structure we want to hash.
150
150
  # @raise [TypedDataError] if the data fails validation.
151
151
  def enforce_typed_data(data)
152
- data = JSON.parse data if Util.is_hex? data
152
+ data = JSON.parse data if Util.hex? data
153
153
  raise TypedDataError, "Data is missing, try again with data." if data.nil? or data.empty?
154
154
  raise TypedDataError, "Data types are missing." if data[:types].nil? or data[:types].empty?
155
155
  raise TypedDataError, "Data primaryType is missing." if data[:primaryType].nil? or data[:primaryType].empty?
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Provides the {Eth} module.
16
+ module Eth
17
+ # Provides ENS specific functionality
18
+ # ref: https://ens.domains
19
+ module Ens
20
+ # Provides EIP-2304 / SLIP-44 cointypes to resolve ENS addresses.
21
+ # ref: https://eips.ethereum.org/EIPS/eip-2304
22
+ module CoinType
23
+ extend self
24
+
25
+ # ENS coin type for Bitcoin.
26
+ BITCOIN = 0.freeze
27
+
28
+ # ENS coin type for Litecoin.
29
+ LITECOIN = 2.freeze
30
+
31
+ # ENS coin type for Dogecoin.
32
+ DOGECOIN = 3.freeze
33
+
34
+ # ENS coin type for Ethereum.
35
+ ETHEREUM = 60.freeze
36
+
37
+ # ENS coin type for Ethereum Classic.
38
+ ETHEREUM_CLASSIC = 61.freeze
39
+
40
+ # ENS coin type for Rootstock.
41
+ ROOTSTOCK = 137.freeze
42
+
43
+ # ENS coin type for Bitcoin Cash.
44
+ BITCOIN_CASH = 145.freeze
45
+
46
+ # ENS coin type for Binance.
47
+ BINANCE = 714.freeze
48
+ end
49
+ end
50
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
16
16
 
17
17
  # Provides the {Eth} module.
18
18
  module Eth
19
+
19
20
  # Provides ENS specific functionality
20
21
  # ref: https://ens.domains
21
22
  module Ens
@@ -23,43 +24,84 @@ module Eth
23
24
  # Utility class for resolving ENS names to Ethereum addresses
24
25
  class Resolver
25
26
 
26
- # The default address for ENS, which applies to most chains
27
- DEFAULT_ADDRESS = "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e".freeze
27
+ # The client instance used to resolve the ENS.
28
+ attr_accessor :client
29
+
30
+ # The address of the ENS registry on the given chain.
31
+ attr_accessor :registry
28
32
 
29
- # Create an instance of the ENS Resolver
33
+ # Create an instance of the ENS Resolver.
30
34
  #
31
- # @param client [Eth::Client] The client instance
32
- # @param address [String] The address of the ENS contract
35
+ # @param client [Eth::Client] The client instance used to resolve the ENS.
36
+ # @param address [String] The address of the ENS registry on the given chain.
33
37
  def initialize(client, address = DEFAULT_ADDRESS)
34
38
  @client = client
35
- @contract = Eth::Contract.from_abi(
36
- name: "ENS",
39
+ @registry = Eth::Contract.from_abi(
40
+ name: "ENSRegistryWithFallback",
37
41
  address: address,
38
- abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abis/ens.json"))),
42
+ abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abi/ens_registry.json"))),
39
43
  )
40
44
  end
41
45
 
42
- # Resolve an ENS name to an address
46
+ # Resolve an ENS name owner.
47
+ #
48
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
49
+ # @return [String] The owner address of the name as a hex string.
50
+ def owner(ens_name)
51
+ @client.call(@registry, "owner", namehash(ens_name))
52
+ end
53
+
54
+ # Retrieve the public resolver for the given ENS name.
43
55
  #
44
- # @param ens_name [String] The ENS name, eg: fancy.eth
45
- # @return [String] The owner address of the name, as a hex string
46
- def resolve(ens_name)
47
- @client.call(@contract, "owner", namehash(ens_name))
56
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
57
+ # @return [Eth::Contract] The public resolver contract that can be used
58
+ # to resolve ENS names.
59
+ def resolver(ens_name)
60
+ address = @client.call(@registry, "resolver", namehash(ens_name))
61
+ Eth::Contract.from_abi(
62
+ name: "ENSPublicResolver",
63
+ address: address,
64
+ abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../abi/ens_resolver.json"))),
65
+ )
66
+ end
67
+
68
+ # Resolve an ENS name to an address.
69
+ #
70
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
71
+ # @return [String] The owner address of the name as a hex string.
72
+ def resolve(ens_name, coin_type = Ens::CoinType::ETHEREUM)
73
+ if coin_type === Ens::CoinType::ETHEREUM
74
+ return @client.call(resolver(ens_name), "addr", namehash(ens_name))
75
+ elsif coin_type === Ens::CoinType::ETHEREUM_CLASSIC
76
+ data = @client.call(resolver(ens_name), "addr", namehash(ens_name), coin_type)
77
+ return Util.bin_to_prefixed_hex data
78
+ else
79
+ raise NotImplementedError, "Coin type #{coin_type} not implemented!"
80
+ end
81
+ end
82
+
83
+ # Resolve a text record for a given ENS name.
84
+ #
85
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
86
+ # @param key [String] The key for the text record, e.g., `url`.
87
+ # @return [String] The text record.
88
+ def text(ens_name, key = "description")
89
+ @client.call(resolver(ens_name), "text", namehash(ens_name), key)
48
90
  end
49
91
 
50
92
  # Generate node for the given domain name
51
93
  # See: https://docs.ens.domains/contract-api-reference/name-processing
52
94
  #
53
- # @param ens_name [String] The ENS name, eg: fancy.eth
54
- # @return [String] The node as a hex string
95
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
96
+ # @return [String] The node as a hex string.
55
97
  def namehash(ens_name)
56
- node = ("0" * 64)
98
+ node = Util.hex_to_bin("0" * 64)
57
99
  name = normalize(ens_name)
58
100
  name.split(".").reverse.each do |label|
59
- hash = Digest::Keccak.new(256).hexdigest(label)
60
- node = Digest::Keccak.new(256).hexdigest([node + hash].pack("H*"))
101
+ hash = Util.keccak256(label)
102
+ node = Util.keccak256(node + hash)
61
103
  end
62
- "0x#{node}"
104
+ Util.bin_to_prefixed_hex node
63
105
  end
64
106
 
65
107
  # Normalize a string as specified by http://unicode.org/reports/tr46/
@@ -67,10 +109,12 @@ module Eth
67
109
  # @param input [String] The input string
68
110
  # @return [String] The normalized output string
69
111
  def normalize(input)
70
- # TODO: This is fairly complicated, and there doesn't seem to be a ruby
71
- # library which can handle it perfectly.
72
- # https://www.unicode.org/reports/tr46/tr46-27.html
73
- input.downcase
112
+ name = input.dup
113
+ if name.gsub!(/[`~!@#$%^&*()_=+\[\]{}<>,;:'"\/\\|?]/, "").nil?
114
+ return input.downcase
115
+ else
116
+ raise ArgumentError, "Provided ENS name contains illegal characters: #{input}"
117
+ end
74
118
  end
75
119
  end
76
120
  end
data/lib/eth/ens.rb ADDED
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "eth/ens/coin_type"
16
+ require "eth/ens/resolver"
17
+
18
+ # Provides the {Eth} module.
19
+ module Eth
20
+ # Provides ENS specific functionality
21
+ # ref: https://ens.domains
22
+ module Ens
23
+ extend self
24
+
25
+ # The default address for ENS, which applies to most chains
26
+ DEFAULT_ADDRESS = Address.new("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e").freeze
27
+ end
28
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
data/lib/eth/key.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ module Eth
51
51
  unless priv.nil?
52
52
 
53
53
  # Converts hex private keys to binary strings.
54
- priv = Util.hex_to_bin priv if Util.is_hex? priv
54
+ priv = Util.hex_to_bin priv if Util.hex? priv
55
55
 
56
56
  # Creates a keypair from existing private key data.
57
57
  key = ctx.key_pair_from_private_key priv
@@ -74,10 +74,10 @@ module Eth
74
74
  compact, recovery_id = context.sign_recoverable(@private_key, blob).compact
75
75
  signature = compact.bytes
76
76
  v = Chain.to_v recovery_id, chain_id
77
- is_leading_zero = true
77
+ leading_zero = true
78
78
  [v].pack("N").unpack("C*").each do |byte|
79
- is_leading_zero = false if byte > 0 and is_leading_zero
80
- signature.append byte unless is_leading_zero and byte === 0
79
+ leading_zero = false if byte > 0 and leading_zero
80
+ signature.append byte unless leading_zero and byte === 0
81
81
  end
82
82
  Util.bin_to_hex signature.pack "c*"
83
83
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ module Eth
31
31
  # @raise [Eth::Rlp::DecodingError] if the input string does not end after
32
32
  # the root item.
33
33
  def perform(rlp)
34
- rlp = Util.hex_to_bin rlp if Util.is_hex? rlp
34
+ rlp = Util.hex_to_bin rlp if Util.hex? rlp
35
35
  rlp = Util.str_to_bytes rlp
36
36
  begin
37
37
  item, next_start = consume_item rlp, 0
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -41,8 +41,8 @@ module Eth
41
41
  # Encodes the raw item.
42
42
  def encode_raw(item)
43
43
  return item if item.instance_of? Rlp::Data
44
- return encode_primitive item if Util.is_primitive? item
45
- return encode_list item if Util.is_list? item
44
+ return encode_primitive item if Util.primitive? item
45
+ return encode_list item if Util.list? item
46
46
  raise EncodingError "Cannot encode object of type #{item.class.name}"
47
47
  end
48
48
 
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2016-2022 The Ruby-Eth Contributors
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.