eth 0.5.9 → 0.5.11
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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/spec.yml +9 -3
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +61 -0
- data/README.md +5 -1
- data/SECURITY.md +24 -0
- data/abi/ens_registry.json +436 -0
- data/abi/ens_resolver.json +885 -0
- data/eth.gemspec +4 -1
- data/lib/eth/abi/decoder.rb +135 -0
- data/lib/eth/abi/encoder.rb +304 -0
- data/lib/eth/abi/event.rb +13 -2
- data/lib/eth/abi/type.rb +1 -1
- data/lib/eth/abi.rb +10 -385
- data/lib/eth/api.rb +45 -51
- data/lib/eth/chain.rb +9 -0
- data/lib/eth/client/http.rb +18 -4
- data/lib/eth/client/ipc.rb +2 -2
- data/lib/eth/client.rb +120 -113
- data/lib/eth/contract.rb +11 -1
- data/lib/eth/ens/coin_type.rb +50 -0
- data/lib/eth/ens/resolver.rb +67 -23
- data/lib/eth/ens.rb +28 -0
- data/lib/eth/solidity.rb +7 -4
- data/lib/eth/tx/eip1559.rb +10 -5
- data/lib/eth/tx/eip2930.rb +10 -5
- data/lib/eth/tx/legacy.rb +0 -2
- data/lib/eth/tx.rb +9 -2
- data/lib/eth/util.rb +1 -0
- data/lib/eth/version.rb +11 -2
- data/lib/eth.rb +1 -1
- metadata +28 -7
- data/abis/ens.json +0 -422
- data/lib/eth/client/http_auth.rb +0 -73
data/lib/eth/client/ipc.rb
CHANGED
@@ -24,7 +24,7 @@ module Eth
|
|
24
24
|
attr_accessor :path
|
25
25
|
|
26
26
|
# Constructor for the IPC Client. Should not be used; use
|
27
|
-
# {Client.create}
|
27
|
+
# {Client.create} instead.
|
28
28
|
#
|
29
29
|
# @param path [String] an URI pointing to an IPC RPC-API.
|
30
30
|
def initialize(path)
|
@@ -36,7 +36,7 @@ module Eth
|
|
36
36
|
#
|
37
37
|
# @param payload [Hash] the RPC request parameters.
|
38
38
|
# @return [String] a JSON-encoded response.
|
39
|
-
def
|
39
|
+
def send_request(payload)
|
40
40
|
socket = UNIXSocket.new(@path)
|
41
41
|
socket.puts(payload)
|
42
42
|
read = socket.recvmsg(nil)[0]
|
data/lib/eth/client.rb
CHANGED
@@ -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
|
48
|
-
# {#
|
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
|
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:
|
142
|
+
gas_limit: Tx::DEFAULT_GAS_LIMIT,
|
138
143
|
chain_id: chain_id,
|
139
144
|
}
|
140
|
-
|
141
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
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
|
-
|
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 }
|
270
|
-
raise ArgumentError, "this function does not exist!" if func.nil?
|
271
|
-
|
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
|
-
|
262
|
+
output[0]
|
274
263
|
else
|
275
|
-
|
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
|
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
|
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)
|
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
|
-
|
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
|
-
|
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(
|
479
|
+
output = JSON.parse(send_request(payload.to_json))
|
472
480
|
raise IOError, output["error"]["message"] unless output["error"].nil?
|
473
|
-
|
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
|
-
|
502
|
+
params.map! { |param| marshal(param) }
|
495
503
|
elsif params.is_a? Hash
|
496
504
|
params = camelize!(params)
|
497
|
-
|
505
|
+
params.transform_values! { |param| marshal(param) }
|
498
506
|
elsif params.is_a? Numeric
|
499
|
-
|
507
|
+
Util.prefix_hex "#{params.to_i.to_s(16)}"
|
500
508
|
elsif params.is_a? Address
|
501
|
-
|
509
|
+
params.to_s
|
502
510
|
elsif Util.hex? params
|
503
|
-
|
511
|
+
Util.prefix_hex params
|
504
512
|
else
|
505
|
-
|
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
|
-
|
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
|
data/lib/eth/ens/resolver.rb
CHANGED
@@ -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
|
27
|
-
|
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
|
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
|
-
@
|
36
|
-
name: "
|
39
|
+
@registry = Eth::Contract.from_abi(
|
40
|
+
name: "ENSRegistryWithFallback",
|
37
41
|
address: address,
|
38
|
-
abi: JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../../
|
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
|
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,
|
45
|
-
# @return [
|
46
|
-
|
47
|
-
|
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,
|
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 =
|
60
|
-
node =
|
101
|
+
hash = Util.keccak256(label)
|
102
|
+
node = Util.keccak256(node + hash)
|
61
103
|
end
|
62
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|