eth 0.5.0 → 0.5.3
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/codeql.yml +4 -0
- data/.github/workflows/spec.yml +14 -3
- data/.yardopts +1 -0
- data/AUTHORS.txt +14 -1
- data/CHANGELOG.md +63 -13
- data/README.md +121 -18
- data/bin/console +2 -1
- data/bin/setup +3 -4
- data/codecov.yml +6 -0
- data/eth.gemspec +5 -7
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +8 -7
- data/lib/eth/abi.rb +29 -11
- data/lib/eth/address.rb +12 -5
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +31 -28
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +468 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/contract/event.rb +41 -0
- data/lib/eth/contract/function.rb +56 -0
- data/lib/eth/contract/function_input.rb +36 -0
- data/lib/eth/contract/function_output.rb +32 -0
- data/lib/eth/contract/initializer.rb +46 -0
- data/lib/eth/contract.rb +120 -0
- data/lib/eth/eip712.rb +2 -2
- data/lib/eth/key/decrypter.rb +16 -13
- data/lib/eth/key/encrypter.rb +27 -25
- data/lib/eth/key.rb +21 -16
- data/lib/eth/rlp/decoder.rb +114 -0
- data/lib/eth/rlp/encoder.rb +78 -0
- data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
- data/lib/eth/rlp/sedes/binary.rb +97 -0
- data/lib/eth/rlp/sedes/list.rb +84 -0
- data/lib/eth/rlp/sedes.rb +74 -0
- data/lib/eth/rlp.rb +63 -0
- data/lib/eth/signature.rb +11 -8
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +22 -14
- data/lib/eth/tx/eip2930.rb +23 -15
- data/lib/eth/tx/legacy.rb +14 -10
- data/lib/eth/tx.rb +28 -34
- data/lib/eth/unit.rb +1 -1
- data/lib/eth/util.rb +68 -11
- data/lib/eth/version.rb +3 -3
- data/lib/eth.rb +15 -2
- metadata +31 -23
- data/lib/eth/abi/constant.rb +0 -63
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2016-2022 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 "socket"
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Eth
|
19
|
+
|
20
|
+
# Provides an IPC-RPC client.
|
21
|
+
class Client::Ipc < Client
|
22
|
+
|
23
|
+
# The path of the IPC socket.
|
24
|
+
attr_accessor :path
|
25
|
+
|
26
|
+
# Constructor for the IPC Client. Should not be used; use
|
27
|
+
# {Client.create} intead.
|
28
|
+
#
|
29
|
+
# @param path [String] an URI pointing to an IPC RPC-API.
|
30
|
+
def initialize(path)
|
31
|
+
super
|
32
|
+
@path = path
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sends an RPC request to the connected IPC socket.
|
36
|
+
#
|
37
|
+
# @param payload [Hash] the RPC request parameters.
|
38
|
+
# @return [String] a JSON-encoded response.
|
39
|
+
def send(payload)
|
40
|
+
socket = UNIXSocket.new(@path)
|
41
|
+
socket.puts(payload)
|
42
|
+
read = socket.recvmsg(nil)[0]
|
43
|
+
until read.end_with?("\n")
|
44
|
+
read = read << socket.recvmsg(nil)[0]
|
45
|
+
end
|
46
|
+
socket.close
|
47
|
+
return read
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/eth/client.rb
ADDED
@@ -0,0 +1,468 @@
|
|
1
|
+
# Copyright (c) 2016-2022 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
|
+
|
18
|
+
# Provides the {Eth::Client} super-class to connect to Ethereum
|
19
|
+
# network's RPC-API endpoints (IPC or HTTP).
|
20
|
+
class Client
|
21
|
+
|
22
|
+
# The client's RPC-request ID starting at 0.
|
23
|
+
attr_reader :id
|
24
|
+
|
25
|
+
# The connected network's chain ID.
|
26
|
+
attr_reader :chain_id
|
27
|
+
|
28
|
+
# The connected network's client coinbase.
|
29
|
+
attr_accessor :default_account
|
30
|
+
|
31
|
+
# The default transaction max priority fee per gas in Wei.
|
32
|
+
attr_accessor :max_priority_fee_per_gas
|
33
|
+
|
34
|
+
# The default transaction max fee per gas in Wei.
|
35
|
+
attr_accessor :max_fee_per_gas
|
36
|
+
|
37
|
+
# The default gas limit for the transaction.
|
38
|
+
attr_accessor :gas_limit
|
39
|
+
|
40
|
+
# Creates a new RPC-Client, either by providing an HTTP/S host or
|
41
|
+
# an IPC path.
|
42
|
+
#
|
43
|
+
# @param host [String] either an HTTP/S host or an IPC path.
|
44
|
+
# @return [Eth::Client::Ipc] an IPC client.
|
45
|
+
# @return [Eth::Client::Http] an HTTP client.
|
46
|
+
# @raise [ArgumentError] in case it cannot determine the client type.
|
47
|
+
def self.create(host)
|
48
|
+
return Client::Ipc.new host if host.end_with? ".ipc"
|
49
|
+
return Client::Http.new host if host.start_with? "http"
|
50
|
+
raise ArgumentError, "Unable to detect client type!"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Constructor for the {Eth::Client} super-class. Should not be used;
|
54
|
+
# use {Client.create} intead.
|
55
|
+
def initialize(_)
|
56
|
+
@id = 0
|
57
|
+
@max_priority_fee_per_gas = 0
|
58
|
+
@max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
|
59
|
+
@gas_limit = Tx::DEFAULT_GAS_LIMIT
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets the default account (coinbase) of the connected client.
|
63
|
+
#
|
64
|
+
# @return [Eth::Address] the coinbase account address.
|
65
|
+
def default_account
|
66
|
+
@default_account ||= Address.new eth_coinbase["result"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Gets the chain ID of the connected network.
|
70
|
+
#
|
71
|
+
# @return [Integer] the chain ID.
|
72
|
+
def chain_id
|
73
|
+
@chain_id ||= eth_chain_id["result"].to_i 16
|
74
|
+
end
|
75
|
+
|
76
|
+
# Gets the balance for an address.
|
77
|
+
#
|
78
|
+
# @param address [Eth::Address] the address to get the balance for.
|
79
|
+
# @return [Integer] the balance in Wei.
|
80
|
+
def get_balance(address)
|
81
|
+
eth_get_balance(address)["result"].to_i 16
|
82
|
+
end
|
83
|
+
|
84
|
+
# Gets the next nonce for an address used to draft new transactions.
|
85
|
+
#
|
86
|
+
# @param address [Eth::Address] the address to get the nonce for.
|
87
|
+
# @return [Integer] the next nonce to be used.
|
88
|
+
def get_nonce(address)
|
89
|
+
eth_get_transaction_count(address, "pending")["result"].to_i 16
|
90
|
+
end
|
91
|
+
|
92
|
+
# Simply transfer Ether to an account and waits for it to be mined.
|
93
|
+
# Uses `eth_coinbase` and external signer if no sender key is
|
94
|
+
# provided.
|
95
|
+
#
|
96
|
+
# @param destination [Eth::Address] the destination address.
|
97
|
+
# @param amount [Integer] the transfer amount in Wei.
|
98
|
+
# @param sender_key [Eth::Key] the sender private key.
|
99
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
100
|
+
# @return [String] the transaction hash.
|
101
|
+
def transfer_and_wait(destination, amount, sender_key = nil, legacy = false)
|
102
|
+
wait_for_tx(transfer(destination, amount, sender_key, legacy))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Simply transfer Ether to an account without any call data or
|
106
|
+
# access lists attached. Uses `eth_coinbase` and external signer
|
107
|
+
# if no sender key is provided.
|
108
|
+
#
|
109
|
+
# @param destination [Eth::Address] the destination address.
|
110
|
+
# @param amount [Integer] the transfer amount in Wei.
|
111
|
+
# @param sender_key [Eth::Key] the sender private key.
|
112
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
113
|
+
# @return [String] the transaction hash.
|
114
|
+
def transfer(destination, amount, sender_key = nil, legacy = false)
|
115
|
+
params = {
|
116
|
+
value: amount,
|
117
|
+
to: destination,
|
118
|
+
gas_limit: gas_limit,
|
119
|
+
chain_id: chain_id,
|
120
|
+
}
|
121
|
+
if legacy
|
122
|
+
params.merge!({
|
123
|
+
gas_price: max_fee_per_gas,
|
124
|
+
})
|
125
|
+
else
|
126
|
+
params.merge!({
|
127
|
+
priority_fee: max_priority_fee_per_gas,
|
128
|
+
max_gas_fee: max_fee_per_gas,
|
129
|
+
})
|
130
|
+
end
|
131
|
+
unless sender_key.nil?
|
132
|
+
|
133
|
+
# use the provided key as sender and signer
|
134
|
+
params.merge!({
|
135
|
+
from: sender_key.address,
|
136
|
+
nonce: get_nonce(sender_key.address),
|
137
|
+
})
|
138
|
+
tx = Eth::Tx.new(params)
|
139
|
+
tx.sign sender_key
|
140
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
141
|
+
else
|
142
|
+
|
143
|
+
# use the default account as sender and external signer
|
144
|
+
params.merge!({
|
145
|
+
from: default_account,
|
146
|
+
nonce: get_nonce(default_account),
|
147
|
+
})
|
148
|
+
return eth_send_transaction(params)["result"]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Deploy contract and waits for it to be mined.
|
153
|
+
# Uses `eth_coinbase` or external signer
|
154
|
+
# if no sender key is provided.
|
155
|
+
#
|
156
|
+
# @overload deploy(contract)
|
157
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
158
|
+
# @overload deploy(contract, sender_key)
|
159
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
160
|
+
# @param sender_key [Eth::Key] the sender private key.
|
161
|
+
# @overload deploy(contract, sender_key, legacy)
|
162
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
163
|
+
# @param sender_key [Eth::Key] the sender private key.
|
164
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
165
|
+
# @return [String] the contract address.
|
166
|
+
def deploy_and_wait(contract, sender_key: nil, legacy: false)
|
167
|
+
hash = wait_for_tx(deploy(contract, sender_key: sender_key, legacy: legacy))
|
168
|
+
contract.address = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Deploy contract. Uses `eth_coinbase` or external signer
|
172
|
+
# if no sender key is provided.
|
173
|
+
#
|
174
|
+
# @overload deploy(contract)
|
175
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
176
|
+
# @overload deploy(contract, sender_key)
|
177
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
178
|
+
# @param sender_key [Eth::Key] the sender private key.
|
179
|
+
# @overload deploy(contract, sender_key, legacy)
|
180
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
181
|
+
# @param sender_key [Eth::Key] the sender private key.
|
182
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
183
|
+
# @return [String] the transaction hash.
|
184
|
+
def deploy(contract, sender_key: nil, legacy: false)
|
185
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
186
|
+
params = {
|
187
|
+
value: 0,
|
188
|
+
gas_limit: gas_limit,
|
189
|
+
chain_id: chain_id,
|
190
|
+
data: contract.bin,
|
191
|
+
}
|
192
|
+
if legacy
|
193
|
+
params.merge!({
|
194
|
+
gas_price: max_fee_per_gas,
|
195
|
+
})
|
196
|
+
else
|
197
|
+
params.merge!({
|
198
|
+
priority_fee: max_priority_fee_per_gas,
|
199
|
+
max_gas_fee: max_fee_per_gas,
|
200
|
+
})
|
201
|
+
end
|
202
|
+
unless sender_key.nil?
|
203
|
+
# use the provided key as sender and signer
|
204
|
+
params.merge!({
|
205
|
+
from: sender_key.address,
|
206
|
+
nonce: get_nonce(sender_key.address),
|
207
|
+
})
|
208
|
+
tx = Eth::Tx.new(params)
|
209
|
+
tx.sign sender_key
|
210
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
211
|
+
else
|
212
|
+
# use the default account as sender and external signer
|
213
|
+
params.merge!({
|
214
|
+
from: default_account,
|
215
|
+
nonce: get_nonce(default_account),
|
216
|
+
})
|
217
|
+
return eth_send_transaction(params)["result"]
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Encoding for function calls.
|
222
|
+
def call_payload(fun, args)
|
223
|
+
types = fun.inputs.map { |i| i.type }
|
224
|
+
encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
|
225
|
+
"0x" + fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Non-transactional function call called from call().
|
229
|
+
#
|
230
|
+
# @overload call_raw(contract, func)
|
231
|
+
# @param contract [Eth::Contract] subject contract to call.
|
232
|
+
# @param func [Eth::Contract::Function] method name to be called.
|
233
|
+
# @overload call_raw(contract, func, value)
|
234
|
+
# @param contract [Eth::Contract] subject contract to call.
|
235
|
+
# @param func [Eth::Contract::Function] method name to be called.
|
236
|
+
# @param value [Integer|String] function arguments.
|
237
|
+
# @overload call_raw(contract, func, value, sender_key, legacy)
|
238
|
+
# @param contract [Eth::Contract] subject contract to call.
|
239
|
+
# @param func [Eth::Contract::Function] method name to be called.
|
240
|
+
# @param value [Integer|String] function arguments.
|
241
|
+
# @param sender_key [Eth::Key] the sender private key.
|
242
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
243
|
+
# @return [Object] returns the result of the call.
|
244
|
+
def call_raw(contract, func, *args, **kwargs)
|
245
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
246
|
+
params = {
|
247
|
+
gas_limit: gas_limit,
|
248
|
+
chain_id: chain_id,
|
249
|
+
data: call_payload(func, args),
|
250
|
+
}
|
251
|
+
if kwargs[:address] || contract.address
|
252
|
+
params.merge!({ to: kwargs[:address] || contract.address })
|
253
|
+
end
|
254
|
+
if kwargs[:legacy]
|
255
|
+
params.merge!({
|
256
|
+
gas_price: max_fee_per_gas,
|
257
|
+
})
|
258
|
+
else
|
259
|
+
params.merge!({
|
260
|
+
priority_fee: max_priority_fee_per_gas,
|
261
|
+
max_gas_fee: max_fee_per_gas,
|
262
|
+
})
|
263
|
+
end
|
264
|
+
unless kwargs[:sender_key].nil?
|
265
|
+
# use the provided key as sender and signer
|
266
|
+
params.merge!({
|
267
|
+
from: kwargs[:sender_key].address,
|
268
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
269
|
+
})
|
270
|
+
tx = Eth::Tx.new(params)
|
271
|
+
tx.sign kwargs[:sender_key]
|
272
|
+
else
|
273
|
+
# use the default account as sender and external signer
|
274
|
+
params.merge!({
|
275
|
+
from: default_account,
|
276
|
+
nonce: get_nonce(default_account),
|
277
|
+
})
|
278
|
+
end
|
279
|
+
raw_result = eth_call(params)["result"]
|
280
|
+
types = func.outputs.map { |i| i.type }
|
281
|
+
Eth::Abi.decode(types, raw_result)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Non-transactional function calls.
|
285
|
+
#
|
286
|
+
# @overload call(contract, function_name)
|
287
|
+
# @param contract [Eth::Contract] subject contract to call.
|
288
|
+
# @param function_name [String] method name to be called.
|
289
|
+
# @overload call(contract, function_name, value)
|
290
|
+
# @param contract [Eth::Contract] subject contract to call.
|
291
|
+
# @param function_name [String] method name to be called.
|
292
|
+
# @param value [Integer|String] function arguments.
|
293
|
+
# @overload call(contract, function_name, value, sender_key, legacy)
|
294
|
+
# @param contract [Eth::Contract] subject contract to call.
|
295
|
+
# @param function_name [String] method name to be called.
|
296
|
+
# @param value [Integer|String] function arguments.
|
297
|
+
# @param sender_key [Eth::Key] the sender private key.
|
298
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
299
|
+
# @return [Object] returns the result of the call.
|
300
|
+
def call(contract, function_name, *args, **kwargs)
|
301
|
+
func = contract.functions.select { |func| func.name == function_name }[0]
|
302
|
+
raise ArgumentError, "function_name does not exist!" if func.nil?
|
303
|
+
output = call_raw(contract, func, *args, **kwargs)
|
304
|
+
if output.length == 1
|
305
|
+
return output[0]
|
306
|
+
else
|
307
|
+
return output
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Function call with transaction.
|
312
|
+
#
|
313
|
+
# @overload transact(contract, function_name)
|
314
|
+
# @param contract [Eth::Contract] subject contract to call.
|
315
|
+
# @param function_name [String] method name to be called.
|
316
|
+
# @overload transact(contract, function_name, value)
|
317
|
+
# @param contract [Eth::Contract] subject contract to call.
|
318
|
+
# @param function_name [String] method name to be called.
|
319
|
+
# @param value [Integer|String] function arguments.
|
320
|
+
# @overload transact(contract, function_name, value, sender_key, legacy, address)
|
321
|
+
# @param contract [Eth::Contract] subject contract to call.
|
322
|
+
# @param function_name [String] method name to be called.
|
323
|
+
# @param value [Integer|String] function arguments.
|
324
|
+
# @param sender_key [Eth::Key] the sender private key.
|
325
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
326
|
+
# @param address [String] contract address.
|
327
|
+
# @return [Object] returns the result of the call.
|
328
|
+
def transact(contract, function_name, *args, **kwargs)
|
329
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
330
|
+
fun = contract.functions.select { |func| func.name == function_name }[0]
|
331
|
+
params = {
|
332
|
+
value: 0,
|
333
|
+
gas_limit: gas_limit,
|
334
|
+
chain_id: chain_id,
|
335
|
+
to: kwargs[:address] || contract.address,
|
336
|
+
data: call_payload(fun, args),
|
337
|
+
}
|
338
|
+
if kwargs[:legacy]
|
339
|
+
params.merge!({
|
340
|
+
gas_price: max_fee_per_gas,
|
341
|
+
})
|
342
|
+
else
|
343
|
+
params.merge!({
|
344
|
+
priority_fee: max_priority_fee_per_gas,
|
345
|
+
max_gas_fee: max_fee_per_gas,
|
346
|
+
})
|
347
|
+
end
|
348
|
+
unless kwargs[:sender_key].nil?
|
349
|
+
# use the provided key as sender and signer
|
350
|
+
params.merge!({
|
351
|
+
from: kwargs[:sender_key].address,
|
352
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
353
|
+
})
|
354
|
+
tx = Eth::Tx.new(params)
|
355
|
+
tx.sign kwargs[:sender_key]
|
356
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
357
|
+
else
|
358
|
+
# use the default account as sender and external signer
|
359
|
+
params.merge!({
|
360
|
+
from: default_account,
|
361
|
+
nonce: get_nonce(default_account),
|
362
|
+
})
|
363
|
+
return eth_send_transaction(params)["result"]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Function call with transaction and waits for it to be mined.
|
368
|
+
#
|
369
|
+
# @overload transact_and_wait(contract, function_name)
|
370
|
+
# @param contract [Eth::Contract] subject contract to call.
|
371
|
+
# @param function_name [String] method name to be called.
|
372
|
+
# @overload transact_and_wait(contract, function_name, value)
|
373
|
+
# @param contract [Eth::Contract] subject contract to call.
|
374
|
+
# @param function_name [String] method name to be called.
|
375
|
+
# @param value [Integer|String] function arguments.
|
376
|
+
# @overload transact_and_wait(contract, function_name, value, sender_key, legacy, address)
|
377
|
+
# @param contract [Eth::Contract] subject contract to call.
|
378
|
+
# @param function_name [String] method name to be called.
|
379
|
+
# @param value [Integer|String] function arguments.
|
380
|
+
# @param sender_key [Eth::Key] the sender private key.
|
381
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
382
|
+
# @param address [String] contract address.
|
383
|
+
# @return [Object] returns the result of the call.
|
384
|
+
def transact_and_wait(contract, function_name, *args, **kwargs)
|
385
|
+
wait_for_tx(transact(contract, function_name, *args, **kwargs))
|
386
|
+
end
|
387
|
+
|
388
|
+
# Gives control over resetting the RPC request ID back to zero.
|
389
|
+
# Usually not needed.
|
390
|
+
#
|
391
|
+
# @return [Integer] 0
|
392
|
+
def reset_id
|
393
|
+
@id = 0
|
394
|
+
end
|
395
|
+
|
396
|
+
# Checkes wether a transaction is mined or not.
|
397
|
+
#
|
398
|
+
# @param hash [String] the transaction hash.
|
399
|
+
# @return [Boolean] true if included in a block.
|
400
|
+
def is_mined_tx?(hash)
|
401
|
+
mined_tx = eth_get_transaction_by_hash hash
|
402
|
+
!mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
|
403
|
+
end
|
404
|
+
|
405
|
+
# Waits for an transaction to be mined by the connected chain.
|
406
|
+
#
|
407
|
+
# @param hash [String] the transaction hash.
|
408
|
+
# @return [String] the transactin hash once the transaction is mined.
|
409
|
+
# @raise [Timeout::Error] if it's not mined within 5 minutes.
|
410
|
+
def wait_for_tx(hash)
|
411
|
+
start_time = Time.now
|
412
|
+
timeout = 300
|
413
|
+
retry_rate = 0.1
|
414
|
+
loop do
|
415
|
+
raise Timeout::Error if ((Time.now - start_time) > timeout)
|
416
|
+
return hash if is_mined_tx? hash
|
417
|
+
sleep retry_rate
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
# Metafunction to provide all known RPC commands defined in
|
422
|
+
# Eth::Api as snake_case methods to the Eth::Client classes.
|
423
|
+
Api::COMMANDS.each do |cmd|
|
424
|
+
method_name = cmd.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
425
|
+
define_method method_name do |*args|
|
426
|
+
send_command cmd, args
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
private
|
431
|
+
|
432
|
+
# Prepares parameters and sends the command to the client.
|
433
|
+
def send_command(command, args)
|
434
|
+
args << "latest" if ["eth_getBalance", "eth_call"].include? command
|
435
|
+
payload = {
|
436
|
+
jsonrpc: "2.0",
|
437
|
+
method: command,
|
438
|
+
params: marshal(args),
|
439
|
+
id: next_id,
|
440
|
+
}
|
441
|
+
output = JSON.parse(send(payload.to_json))
|
442
|
+
raise IOError, output["error"]["message"] unless output["error"].nil?
|
443
|
+
return output
|
444
|
+
end
|
445
|
+
|
446
|
+
# Increments the request id.
|
447
|
+
def next_id
|
448
|
+
@id += 1
|
449
|
+
end
|
450
|
+
|
451
|
+
# Recursively marshals all request parameters.
|
452
|
+
def marshal(params)
|
453
|
+
if params.is_a? Array
|
454
|
+
return params.map! { |param| marshal(param) }
|
455
|
+
elsif params.is_a? Hash
|
456
|
+
return params.transform_values! { |param| marshal(param) }
|
457
|
+
elsif params.is_a? Numeric
|
458
|
+
return Util.prefix_hex "#{params.to_i.to_s(16)}"
|
459
|
+
elsif params.is_a? Address
|
460
|
+
return params.to_s
|
461
|
+
elsif Util.is_hex? params
|
462
|
+
return Util.prefix_hex params
|
463
|
+
else
|
464
|
+
return params
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
data/lib/eth/constant.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Copyright (c) 2016-2022 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
|
+
# -*- encoding : ascii-8bit -*-
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Eth
|
19
|
+
|
20
|
+
# Provides commonly used constants, such as zero bytes or zero keys.
|
21
|
+
module Constant
|
22
|
+
|
23
|
+
# The empty byte is defined as "".
|
24
|
+
BYTE_EMPTY = "".freeze
|
25
|
+
|
26
|
+
# The zero byte is 0x00.
|
27
|
+
BYTE_ZERO = "\x00".freeze
|
28
|
+
|
29
|
+
# The byte one is 0x01.
|
30
|
+
BYTE_ONE = "\x01".freeze
|
31
|
+
|
32
|
+
# The size of a 32-bit number.
|
33
|
+
TT32 = (2 ** 32).freeze
|
34
|
+
|
35
|
+
# The size of a 256-bit number.
|
36
|
+
TT256 = (2 ** 256).freeze
|
37
|
+
|
38
|
+
# The maximum possible value of an UInt256.
|
39
|
+
UINT_MAX = (2 ** 256 - 1).freeze
|
40
|
+
|
41
|
+
# The minimum possible value of an UInt256.
|
42
|
+
UINT_MIN = 0.freeze
|
43
|
+
|
44
|
+
# The maximum possible value of an Int256.
|
45
|
+
INT_MAX = (2 ** 255 - 1).freeze
|
46
|
+
|
47
|
+
# The minimum possible value of an Int256.
|
48
|
+
INT_MIN = (-2 ** 255).freeze
|
49
|
+
|
50
|
+
# A hash containing only zeros.
|
51
|
+
HASH_ZERO = ("\x00" * 32).freeze
|
52
|
+
|
53
|
+
# The RLP short length limit.
|
54
|
+
SHORT_LENGTH_LIMIT = 56.freeze
|
55
|
+
|
56
|
+
# The RLP long length limit.
|
57
|
+
LONG_LENGTH_LIMIT = (256 ** 8).freeze
|
58
|
+
|
59
|
+
# The RLP primitive type offset.
|
60
|
+
PRIMITIVE_PREFIX_OFFSET = 0x80.freeze
|
61
|
+
|
62
|
+
# The RLP array type offset.
|
63
|
+
LIST_PREFIX_OFFSET = 0xc0.freeze
|
64
|
+
|
65
|
+
# The binary encoding is ASCII (8-bit).
|
66
|
+
BINARY_ENCODING = "ASCII-8BIT".freeze
|
67
|
+
|
68
|
+
# Infinity as constant for convenience.
|
69
|
+
INFINITY = (1.0 / 0.0).freeze
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Copyright (c) 2016-2022 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
|
+
# -*- encoding : ascii-8bit -*-
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Eth
|
19
|
+
# Provide classes for contract event.
|
20
|
+
class Contract::Event
|
21
|
+
attr_accessor :name, :signature, :input_types, :inputs, :event_string, :address
|
22
|
+
|
23
|
+
# Constructor of the {Eth::Contract::Event} class.
|
24
|
+
#
|
25
|
+
# @param data [Hash] contract event data.
|
26
|
+
def initialize(data)
|
27
|
+
@name = data["name"]
|
28
|
+
@input_types = data["inputs"].collect { |x| x["type"] }
|
29
|
+
@inputs = data["inputs"].collect { |x| x["name"] }
|
30
|
+
@event_string = "#{@name}(#{@input_types.join(",")})"
|
31
|
+
@signature = Digest::Keccak.hexdigest(@event_string, 256)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set the address of the smart contract
|
35
|
+
#
|
36
|
+
# @param address [String] contract address.
|
37
|
+
def set_address(address)
|
38
|
+
@address = address.nil? ? nil : Eth::Address.new(address).address
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (c) 2016-2022 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
|
+
# -*- encoding : ascii-8bit -*-
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Eth
|
19
|
+
# Provides the methods for smart contract function.
|
20
|
+
class Contract::Function
|
21
|
+
attr_accessor :name, :inputs, :outputs, :signature, :constant, :function_string
|
22
|
+
|
23
|
+
# Constructor of the {Eth::Function} class.
|
24
|
+
#
|
25
|
+
# @param data [Hash] function input and output data.
|
26
|
+
def initialize(data)
|
27
|
+
@name = data["name"]
|
28
|
+
@constant = data["constant"]
|
29
|
+
@inputs = data["inputs"].map do |input|
|
30
|
+
Eth::Contract::FunctionInput.new(input)
|
31
|
+
end
|
32
|
+
@outputs = data["outputs"].collect do |output|
|
33
|
+
Eth::Contract::FunctionOutput.new(output)
|
34
|
+
end
|
35
|
+
@function_string = self.class.calc_signature(@name, @inputs)
|
36
|
+
@signature = self.class.encoded_function_signature(@function_string)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create function strings.
|
40
|
+
#
|
41
|
+
# @param name [String] function name.
|
42
|
+
# @param inputs [Array<Eth::Contract::FunctionInput>] function input class list.
|
43
|
+
# @return [String] function string.
|
44
|
+
def self.calc_signature(name, inputs)
|
45
|
+
"#{name}(#{inputs.collect { |x| x.type }.join(",")})"
|
46
|
+
end
|
47
|
+
|
48
|
+
# encode function signature.
|
49
|
+
#
|
50
|
+
# @param signature [String] function signature.
|
51
|
+
# @return [String] encoded function signature string.
|
52
|
+
def self.encoded_function_signature(signature)
|
53
|
+
Digest::Keccak.hexdigest(signature, 256)[0..7]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|