eth 0.5.9 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -34,28 +34,22 @@ 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
39
-
40
37
  # A custom error type if a contract interaction fails.
41
38
  class ContractExecutionError < StandardError; end
42
39
 
43
40
  # Creates a new RPC-Client, either by providing an HTTP/S host or
44
41
  # an IPC path. Supports basic authentication with username and password.
45
42
  #
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.
43
+ # **Note**, this sets the folling gas defaults: {Tx::DEFAULT_PRIORITY_FEE}
44
+ # and {Tx::DEFAULT_GAS_PRICE. Use {#max_priority_fee_per_gas} and
45
+ # {#max_fee_per_gas} to set custom values prior to submitting transactions.
50
46
  #
51
47
  # @param host [String] either an HTTP/S host or an IPC path.
52
48
  # @return [Eth::Client::Ipc] an IPC client.
53
- # @return [Eth::Client::HttpAuth] an HTTP client with basic authentication.
54
49
  # @return [Eth::Client::Http] an HTTP client.
55
50
  # @raise [ArgumentError] in case it cannot determine the client type.
56
51
  def self.create(host)
57
52
  return Client::Ipc.new host if host.end_with? ".ipc"
58
- return Client::HttpAuth.new host if Regexp.new(":.*@.*:", Regexp::IGNORECASE).match host
59
53
  return Client::Http.new host if host.start_with? "http"
60
54
  raise ArgumentError, "Unable to detect client type!"
61
55
  end
@@ -66,7 +60,6 @@ module Eth
66
60
  @id = 0
67
61
  @max_priority_fee_per_gas = Tx::DEFAULT_PRIORITY_FEE
68
62
  @max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
69
- @gas_limit = Tx::DEFAULT_GAS_LIMIT
70
63
  end
71
64
 
72
65
  # Gets the default account (coinbase) of the connected client.
@@ -76,6 +69,7 @@ module Eth
76
69
  #
77
70
  # @return [Eth::Address] the coinbase account address.
78
71
  def default_account
72
+ raise ArgumentError, "The default account is not available on remote connections!" unless local? || @default_account
79
73
  @default_account ||= Address.new eth_coinbase["result"]
80
74
  end
81
75
 
@@ -102,6 +96,17 @@ module Eth
102
96
  eth_get_transaction_count(address, "pending")["result"].to_i 16
103
97
  end
104
98
 
99
+ # Resolves an ENS name to an Ethereum address on the connected chain.
100
+ #
101
+ # @param ens_name [String] The ENS name, e.g., `fancy.eth`.
102
+ # @param registry [String] the address for the ENS registry.
103
+ # @param coin_type [Integer] the coin type as per EIP-2304.
104
+ # @return [Eth::Address] the Ethereum address resolved from the ENS record.
105
+ def resolve_ens(ens_name, registry = Ens::DEFAULT_ADDRESS, coin_type = Ens::CoinType::ETHEREUM)
106
+ ens = Ens::Resolver.new(self, registry)
107
+ ens.resolve(ens_name, coin_type)
108
+ end
109
+
105
110
  # Simply transfer Ether to an account and waits for it to be mined.
106
111
  # Uses `eth_coinbase` and external signer if no sender key is
107
112
  # provided.
@@ -118,7 +123,7 @@ module Eth
118
123
  # if no sender key is provided.
119
124
  #
120
125
  # **Note**, that many remote providers (e.g., Infura) do not provide
121
- # any accounts. Provide a `sender_key` if you experience issues.
126
+ # any accounts. Provide a `sender_key:` if you experience issues.
122
127
  #
123
128
  # @overload transfer(destination, amount)
124
129
  # @param destination [Eth::Address] the destination address.
@@ -134,38 +139,43 @@ module Eth
134
139
  params = {
135
140
  value: amount,
136
141
  to: destination,
137
- gas_limit: gas_limit,
142
+ gas_limit: Tx::DEFAULT_GAS_LIMIT,
138
143
  chain_id: chain_id,
139
144
  }
140
- if kwargs[:legacy]
141
- params.merge!({
142
- gas_price: max_fee_per_gas,
143
- })
144
- else
145
- params.merge!({
146
- priority_fee: max_priority_fee_per_gas,
147
- max_gas_fee: max_fee_per_gas,
148
- })
149
- end
150
- unless kwargs[:sender_key].nil?
145
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
146
+ end
151
147
 
152
- # use the provided key as sender and signer
153
- params.merge!({
154
- from: kwargs[:sender_key].address,
155
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
156
- })
157
- tx = Eth::Tx.new(params)
158
- tx.sign kwargs[:sender_key]
159
- return eth_send_raw_transaction(tx.hex)["result"]
160
- else
148
+ # Transfers a token that implements the ERC20 `transfer()` interface.
149
+ #
150
+ # See {#transfer_erc20} for params and overloads.
151
+ #
152
+ # @return [Object] returns the result of the transaction.
153
+ def transfer_erc20_and_wait(erc20_contract, destination, amount, **kwargs)
154
+ transact_and_wait(erc20_contract, "transfer", destination, amount, **kwargs)
155
+ end
161
156
 
162
- # use the default account as sender and external signer
163
- params.merge!({
164
- from: default_account,
165
- nonce: kwargs[:nonce] || get_nonce(default_account),
166
- })
167
- return eth_send_transaction(params)["result"]
168
- end
157
+ # Transfers a token that implements the ERC20 `transfer()` interface.
158
+ #
159
+ # **Note**, that many remote providers (e.g., Infura) do not provide
160
+ # any accounts. Provide a `sender_key:` if you experience issues.
161
+ #
162
+ # @overload transfer_erc20(erc20_contract, destination, amount)
163
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
164
+ # @param destination [Eth::Address] the destination address.
165
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
166
+ # @overload transfer_erc20(erc20_contract, destination, amount, **kwargs)
167
+ # @param erc20_contract [Eth::Contract] the ERC20 contract to write to.
168
+ # @param destination [Eth::Address] the destination address.
169
+ # @param amount [Integer] the transfer amount (mind the `decimals()`).
170
+ # @param **sender_key [Eth::Key] the sender private key.
171
+ # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
172
+ # @param **gas_limit [Integer] optional gas limit override for the transfer.
173
+ # @param **nonce [Integer] optional specific nonce for transaction.
174
+ # @param **tx_value [Integer] optional transaction value field filling.
175
+ # @return [Object] returns the result of the transaction.
176
+ def transfer_erc20(erc20_contract, destination, amount, **kwargs)
177
+ destination = destination.to_s if destination.instance_of? Eth::Address
178
+ transact(erc20_contract, "transfer", destination, amount, **kwargs)
169
179
  end
170
180
 
171
181
  # Deploys a contract and waits for it to be mined. Uses
@@ -184,7 +194,7 @@ module Eth
184
194
  # if no sender key is provided.
185
195
  #
186
196
  # **Note**, that many remote providers (e.g., Infura) do not provide
187
- # any accounts. Provide a `sender_key` if you experience issues.
197
+ # any accounts. Provide a `sender_key:` if you experience issues.
188
198
  #
189
199
  # @overload deploy(contract)
190
200
  # @param contract [Eth::Contract] contracts to deploy.
@@ -218,33 +228,7 @@ module Eth
218
228
  chain_id: chain_id,
219
229
  data: data,
220
230
  }
221
- if kwargs[:legacy]
222
- params.merge!({
223
- gas_price: max_fee_per_gas,
224
- })
225
- else
226
- params.merge!({
227
- priority_fee: max_priority_fee_per_gas,
228
- max_gas_fee: max_fee_per_gas,
229
- })
230
- end
231
- unless kwargs[:sender_key].nil?
232
- # Uses the provided key as sender and signer
233
- params.merge!({
234
- from: kwargs[:sender_key].address,
235
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
236
- })
237
- tx = Eth::Tx.new(params)
238
- tx.sign kwargs[:sender_key]
239
- return eth_send_raw_transaction(tx.hex)["result"]
240
- else
241
- # Uses the default account as sender and external signer
242
- params.merge!({
243
- from: default_account,
244
- nonce: kwargs[:nonce] || get_nonce(default_account),
245
- })
246
- return eth_send_transaction(params)["result"]
247
- end
231
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
248
232
  end
249
233
 
250
234
  # Calls a contract function without executing it
@@ -263,16 +247,21 @@ module Eth
263
247
  # @param *args optional function arguments.
264
248
  # @param **sender_key [Eth::Key] the sender private key.
265
249
  # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
266
- # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
267
250
  # @return [Object] returns the result of the call.
268
251
  def call(contract, function, *args, **kwargs)
269
- func = contract.functions.select { |func| func.name == function }[0]
270
- raise ArgumentError, "this function does not exist!" if func.nil?
271
- output = call_raw(contract, func, *args, **kwargs)
252
+ func = contract.functions.select { |func| func.name == function }
253
+ raise ArgumentError, "this function does not exist!" if func.nil? || func.size === 0
254
+ selected_func = func.first
255
+ func.each do |f|
256
+ if f.inputs.size === args.size
257
+ selected_func = f
258
+ end
259
+ end
260
+ output = call_raw(contract, selected_func, *args, **kwargs)
272
261
  if output&.length == 1
273
- return output[0]
262
+ output[0]
274
263
  else
275
- return output
264
+ output
276
265
  end
277
266
  end
278
267
 
@@ -280,7 +269,7 @@ module Eth
280
269
  # contract read/write).
281
270
  #
282
271
  # **Note**, that many remote providers (e.g., Infura) do not provide
283
- # any accounts. Provide a `sender_key` if you experience issues.
272
+ # any accounts. Provide a `sender_key:` if you experience issues.
284
273
  #
285
274
  # @overload transact(contract, function)
286
275
  # @param contract [Eth::Contract] the subject contract to write to.
@@ -296,7 +285,7 @@ module Eth
296
285
  # @param **sender_key [Eth::Key] the sender private key.
297
286
  # @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
298
287
  # @param **address [Eth::Address] contract address.
299
- # @param **gas_limit [Integer] optional gas limit override for deploying the contract.
288
+ # @param **gas_limit [Integer] optional gas limit override for transacting with the contract.
300
289
  # @param **nonce [Integer] optional specific nonce for transaction.
301
290
  # @param **tx_value [Integer] optional transaction value field filling.
302
291
  # @return [Object] returns the result of the transaction.
@@ -304,7 +293,7 @@ module Eth
304
293
  gas_limit = if kwargs[:gas_limit]
305
294
  kwargs[:gas_limit]
306
295
  else
307
- Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
296
+ Tx.estimate_intrinsic_gas(contract.bin)
308
297
  end
309
298
  fun = contract.functions.select { |func| func.name == function }[0]
310
299
  params = {
@@ -314,33 +303,7 @@ module Eth
314
303
  to: kwargs[:address] || contract.address,
315
304
  data: call_payload(fun, args),
316
305
  }
317
- if kwargs[:legacy]
318
- params.merge!({
319
- gas_price: max_fee_per_gas,
320
- })
321
- else
322
- params.merge!({
323
- priority_fee: max_priority_fee_per_gas,
324
- max_gas_fee: max_fee_per_gas,
325
- })
326
- end
327
- unless kwargs[:sender_key].nil?
328
- # use the provided key as sender and signer
329
- params.merge!({
330
- from: kwargs[:sender_key].address,
331
- nonce: kwargs[:nonce] || get_nonce(kwargs[:sender_key].address),
332
- })
333
- tx = Eth::Tx.new(params)
334
- tx.sign kwargs[:sender_key]
335
- return eth_send_raw_transaction(tx.hex)["result"]
336
- else
337
- # use the default account as sender and external signer
338
- params.merge!({
339
- from: default_account,
340
- nonce: kwargs[:nonce] || get_nonce(default_account),
341
- })
342
- return eth_send_transaction(params)["result"]
343
- end
306
+ send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
344
307
  end
345
308
 
346
309
  # Executes a contract function with a transaction and waits for it
@@ -375,7 +338,7 @@ module Eth
375
338
  signature = Util.hex_to_bin signature if Util.hex? signature
376
339
  magic = Util.hex_to_bin magic if Util.hex? magic
377
340
  result = call(contract, "isValidSignature", hash, signature)
378
- return result === magic
341
+ result === magic
379
342
  end
380
343
 
381
344
  # Gives control over resetting the RPC request ID back to zero.
@@ -431,6 +394,51 @@ module Eth
431
394
 
432
395
  private
433
396
 
397
+ # Allows to determine if we work with a local connectoin
398
+ def local?
399
+ if self.instance_of? Eth::Client::Ipc
400
+ true
401
+ elsif self.host === "127.0.0.1" || self.host === "localhost"
402
+ true
403
+ else
404
+ false
405
+ end
406
+ end
407
+
408
+ # Prepares a transaction to be send for the given params.
409
+ def send_transaction(params, legacy, key, nonce)
410
+ if legacy
411
+ params.merge!({ gas_price: max_fee_per_gas })
412
+ else
413
+ params.merge!({
414
+ priority_fee: max_priority_fee_per_gas,
415
+ max_gas_fee: max_fee_per_gas,
416
+ })
417
+ end
418
+ unless key.nil?
419
+
420
+ # use the provided key as sender and signer
421
+ params.merge!({
422
+ from: key.address,
423
+ nonce: nonce || get_nonce(key.address),
424
+ })
425
+ tx = Eth::Tx.new(params)
426
+ tx.sign key
427
+ eth_send_raw_transaction(tx.hex)["result"]
428
+ else
429
+
430
+ # do not allow accessing accounts on remote connections
431
+ raise ArgumentError, "The default account is not available on remote connections, please provide a :sender_key!" unless local?
432
+
433
+ # use the default account as sender and external signer
434
+ params.merge!({
435
+ from: default_account,
436
+ nonce: nonce || get_nonce(default_account),
437
+ })
438
+ eth_send_transaction(params)["result"]
439
+ end
440
+ end
441
+
434
442
  # Non-transactional function call called from call().
435
443
  # @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
436
444
  def call_raw(contract, func, *args, **kwargs)
@@ -468,9 +476,9 @@ module Eth
468
476
  params: marshal(args),
469
477
  id: next_id,
470
478
  }
471
- output = JSON.parse(send(payload.to_json))
479
+ output = JSON.parse(send_request(payload.to_json))
472
480
  raise IOError, output["error"]["message"] unless output["error"].nil?
473
- return output
481
+ output
474
482
  end
475
483
 
476
484
  # Increments the request id.
@@ -491,18 +499,18 @@ module Eth
491
499
  def marshal(params)
492
500
  params = params.dup
493
501
  if params.is_a? Array
494
- return params.map! { |param| marshal(param) }
502
+ params.map! { |param| marshal(param) }
495
503
  elsif params.is_a? Hash
496
504
  params = camelize!(params)
497
- return params.transform_values! { |param| marshal(param) }
505
+ params.transform_values! { |param| marshal(param) }
498
506
  elsif params.is_a? Numeric
499
- return Util.prefix_hex "#{params.to_i.to_s(16)}"
507
+ Util.prefix_hex "#{params.to_i.to_s(16)}"
500
508
  elsif params.is_a? Address
501
- return params.to_s
509
+ params.to_s
502
510
  elsif Util.hex? params
503
- return Util.prefix_hex params
511
+ Util.prefix_hex params
504
512
  else
505
- return params
513
+ params
506
514
  end
507
515
  end
508
516
  end
@@ -510,5 +518,4 @@ end
510
518
 
511
519
  # Load the client/* libraries
512
520
  require "eth/client/http"
513
- require "eth/client/http_auth"
514
521
  require "eth/client/ipc"
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)
@@ -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
@@ -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
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