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