eth-custom 0.5.7
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 +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/codeql.yml +48 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +52 -0
- data/.gitignore +43 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.yardopts +1 -0
- data/AUTHORS.txt +29 -0
- data/CHANGELOG.md +218 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +202 -0
- data/README.md +347 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/codecov.yml +6 -0
- data/eth.gemspec +51 -0
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +178 -0
- data/lib/eth/abi.rb +446 -0
- data/lib/eth/address.rb +106 -0
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +157 -0
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +499 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/contract/event.rb +42 -0
- data/lib/eth/contract/function.rb +57 -0
- data/lib/eth/contract/function_input.rb +38 -0
- data/lib/eth/contract/function_output.rb +37 -0
- data/lib/eth/contract/initializer.rb +47 -0
- data/lib/eth/contract.rb +143 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +146 -0
- data/lib/eth/key/encrypter.rb +207 -0
- data/lib/eth/key.rb +167 -0
- 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 +163 -0
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +337 -0
- data/lib/eth/tx/eip2930.rb +329 -0
- data/lib/eth/tx/legacy.rb +297 -0
- data/lib/eth/tx.rb +322 -0
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +235 -0
- data/lib/eth/version.rb +20 -0
- data/lib/eth.rb +35 -0
- metadata +184 -0
data/lib/eth/client.rb
ADDED
@@ -0,0 +1,499 @@
|
|
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
|
+
# Deploys a contract and waits for it to be mined. Uses
|
153
|
+
# `eth_coinbase` or external signer if no sender key is provided.
|
154
|
+
#
|
155
|
+
# @overload deploy(contract)
|
156
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
157
|
+
# @overload deploy(contract, *args, **kwargs)
|
158
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
159
|
+
# *args Optional variable constructor parameter list
|
160
|
+
# **sender_key [Eth::Key] the sender private key.
|
161
|
+
# **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
162
|
+
# **gas_limit [Integer] optional gas limit override for deploying the contract.
|
163
|
+
# @return [String] the contract address.
|
164
|
+
def deploy_and_wait(contract, *args, **kwargs)
|
165
|
+
hash = wait_for_tx(deploy(contract, *args, **kwargs))
|
166
|
+
addr = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
|
167
|
+
contract.address = Address.new(addr).to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
# Deploys a contract. Uses `eth_coinbase` or external signer
|
171
|
+
# if no sender key is provided.
|
172
|
+
#
|
173
|
+
# @overload deploy(contract)
|
174
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
175
|
+
# @overload deploy(contract, *args, **kwargs)
|
176
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
177
|
+
# *args Optional variable constructor parameter list
|
178
|
+
# **sender_key [Eth::Key] the sender private key.
|
179
|
+
# **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
180
|
+
# **gas_limit [Integer] optional gas limit override for deploying the contract.
|
181
|
+
# @return [String] the transaction hash.
|
182
|
+
# @raise [ArgumentError] in case the contract does not have any source.
|
183
|
+
def deploy(contract, *args, **kwargs)
|
184
|
+
raise ArgumentError, "Cannot deploy contract without source or binary!" if contract.bin.nil?
|
185
|
+
raise ArgumentError, "Missing contract constructor params!" if contract.constructor_inputs.length != args.length
|
186
|
+
data = contract.bin
|
187
|
+
unless args.empty?
|
188
|
+
data += encode_constructor_params(contract, args)
|
189
|
+
end
|
190
|
+
gas_limit = if kwargs[:gas_limit]
|
191
|
+
kwargs[:gas_limit]
|
192
|
+
else
|
193
|
+
Tx.estimate_intrinsic_gas(data) + Tx::CREATE_GAS
|
194
|
+
end
|
195
|
+
params = {
|
196
|
+
value: 0,
|
197
|
+
gas_limit: gas_limit,
|
198
|
+
chain_id: chain_id,
|
199
|
+
data: data,
|
200
|
+
}
|
201
|
+
if kwargs[:legacy]
|
202
|
+
params.merge!({
|
203
|
+
gas_price: max_fee_per_gas,
|
204
|
+
})
|
205
|
+
else
|
206
|
+
params.merge!({
|
207
|
+
priority_fee: max_priority_fee_per_gas,
|
208
|
+
max_gas_fee: max_fee_per_gas,
|
209
|
+
})
|
210
|
+
end
|
211
|
+
unless kwargs[:sender_key].nil?
|
212
|
+
# Uses the provided key as sender and signer
|
213
|
+
params.merge!({
|
214
|
+
from: kwargs[:sender_key].address,
|
215
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
216
|
+
})
|
217
|
+
tx = Eth::Tx.new(params)
|
218
|
+
tx.sign kwargs[:sender_key]
|
219
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
220
|
+
else
|
221
|
+
# Uses the default account as sender and external signer
|
222
|
+
params.merge!({
|
223
|
+
from: default_account,
|
224
|
+
nonce: get_nonce(default_account),
|
225
|
+
})
|
226
|
+
return eth_send_transaction(params)["result"]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Calls a contract function without executing it
|
231
|
+
# (non-transactional contract read).
|
232
|
+
#
|
233
|
+
# @overload call(contract, function_name)
|
234
|
+
# @param contract [Eth::Contract] subject contract to call.
|
235
|
+
# @param function_name [String] method name to be called.
|
236
|
+
# @overload call(contract, function_name, value)
|
237
|
+
# @param contract [Eth::Contract] subject contract to call.
|
238
|
+
# @param function_name [String] method name to be called.
|
239
|
+
# @param value [Integer|String] function arguments.
|
240
|
+
# @overload call(contract, function_name, value, sender_key, legacy, gas_limit)
|
241
|
+
# @param contract [Eth::Contract] subject contract to call.
|
242
|
+
# @param function_name [String] method name to be called.
|
243
|
+
# @param value [Integer|String] function arguments.
|
244
|
+
# @param sender_key [Eth::Key] the sender private key.
|
245
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
246
|
+
# @param gas_limit [Integer] optional gas limit override for deploying the contract.
|
247
|
+
# @return [Object] returns the result of the call.
|
248
|
+
def call(contract, function_name, *args, **kwargs)
|
249
|
+
func = contract.functions.select { |func| func.name == function_name }[0]
|
250
|
+
raise ArgumentError, "function_name does not exist!" if func.nil?
|
251
|
+
output = call_raw(contract, func, *args, **kwargs)
|
252
|
+
if output&.length == 1
|
253
|
+
return output[0]
|
254
|
+
else
|
255
|
+
return output
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Executes a contract function with a transaction (transactional
|
260
|
+
# contract read/write).
|
261
|
+
#
|
262
|
+
# @overload transact(contract, function_name)
|
263
|
+
# @param contract [Eth::Contract] subject contract to call.
|
264
|
+
# @param function_name [String] method name to be called.
|
265
|
+
# @overload transact(contract, function_name, value)
|
266
|
+
# @param contract [Eth::Contract] subject contract to call.
|
267
|
+
# @param function_name [String] method name to be called.
|
268
|
+
# @param value [Integer|String] function arguments.
|
269
|
+
# @overload transact(contract, function_name, value, sender_key, legacy, address, gas_limit)
|
270
|
+
# @param contract [Eth::Contract] subject contract to call.
|
271
|
+
# @param function_name [String] method name to be called.
|
272
|
+
# @param value [Integer|String] function arguments.
|
273
|
+
# @param sender_key [Eth::Key] the sender private key.
|
274
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
275
|
+
# @param address [String] contract address.
|
276
|
+
# @param gas_limit [Integer] optional gas limit override for deploying the contract.
|
277
|
+
# @return [Object] returns the result of the call.
|
278
|
+
def transact(contract, function_name, *args, **kwargs)
|
279
|
+
gas_limit = if kwargs[:gas_limit]
|
280
|
+
kwargs[:gas_limit]
|
281
|
+
else
|
282
|
+
Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
283
|
+
end
|
284
|
+
fun = contract.functions.select { |func| func.name == function_name }[0]
|
285
|
+
params = {
|
286
|
+
value: 0,
|
287
|
+
gas_limit: gas_limit,
|
288
|
+
chain_id: chain_id,
|
289
|
+
to: kwargs[:address] || contract.address,
|
290
|
+
data: call_payload(fun, args),
|
291
|
+
}
|
292
|
+
if kwargs[:legacy]
|
293
|
+
params.merge!({
|
294
|
+
gas_price: max_fee_per_gas,
|
295
|
+
})
|
296
|
+
else
|
297
|
+
params.merge!({
|
298
|
+
priority_fee: max_priority_fee_per_gas,
|
299
|
+
max_gas_fee: max_fee_per_gas,
|
300
|
+
})
|
301
|
+
end
|
302
|
+
unless kwargs[:sender_key].nil?
|
303
|
+
# use the provided key as sender and signer
|
304
|
+
params.merge!({
|
305
|
+
from: kwargs[:sender_key].address,
|
306
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
307
|
+
})
|
308
|
+
tx = Eth::Tx.new(params)
|
309
|
+
tx.sign kwargs[:sender_key]
|
310
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
311
|
+
else
|
312
|
+
# use the default account as sender and external signer
|
313
|
+
params.merge!({
|
314
|
+
from: default_account,
|
315
|
+
nonce: get_nonce(default_account),
|
316
|
+
})
|
317
|
+
return eth_send_transaction(params)["result"]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Executes a contract function with a transaction and waits for it
|
322
|
+
# to be mined (transactional contract read/write).
|
323
|
+
#
|
324
|
+
# @overload transact_and_wait(contract, function_name)
|
325
|
+
# @param contract [Eth::Contract] subject contract to call.
|
326
|
+
# @param function_name [String] method name to be called.
|
327
|
+
# @overload transact_and_wait(contract, function_name, value)
|
328
|
+
# @param contract [Eth::Contract] subject contract to call.
|
329
|
+
# @param function_name [String] method name to be called.
|
330
|
+
# @param value [Integer|String] function arguments.
|
331
|
+
# @overload transact_and_wait(contract, function_name, value, sender_key, legacy, address)
|
332
|
+
# @param contract [Eth::Contract] subject contract to call.
|
333
|
+
# @param function_name [String] method name to be called.
|
334
|
+
# @param value [Integer|String] function arguments.
|
335
|
+
# @param sender_key [Eth::Key] the sender private key.
|
336
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
337
|
+
# @param address [String] contract address.
|
338
|
+
# @return [Object] returns the result of the call.
|
339
|
+
def transact_and_wait(contract, function_name, *args, **kwargs)
|
340
|
+
wait_for_tx(transact(contract, function_name, *args, **kwargs))
|
341
|
+
end
|
342
|
+
|
343
|
+
# Provides an interface to call `isValidSignature` as per EIP-1271 on a given
|
344
|
+
# smart contract to verify the given hash and signature matching the magic
|
345
|
+
# value.
|
346
|
+
#
|
347
|
+
# @param contract [Eth::Contract] a deployed contract implementing EIP-1271.
|
348
|
+
# @param hash [String] the message hash to be checked against the signature.
|
349
|
+
# @param signature [String] the signature to be recovered by the contract.
|
350
|
+
# @param magic [String] the expected magic value (defaults to `1626ba7e`).
|
351
|
+
# @return [Boolean] true if magic matches and signature is valid.
|
352
|
+
# @raise [ArgumentError] in case the contract cannot be called yet.
|
353
|
+
def is_valid_signature(contract, hash, signature, magic = "1626ba7e")
|
354
|
+
raise ArgumentError, "Contract not deployed yet." if contract.address.nil?
|
355
|
+
hash = Util.hex_to_bin hash if Util.is_hex? hash
|
356
|
+
signature = Util.hex_to_bin signature if Util.is_hex? signature
|
357
|
+
magic = Util.hex_to_bin magic if Util.is_hex? magic
|
358
|
+
result = call(contract, "isValidSignature", hash, signature)
|
359
|
+
return result === magic
|
360
|
+
end
|
361
|
+
|
362
|
+
# Gives control over resetting the RPC request ID back to zero.
|
363
|
+
# Usually not needed.
|
364
|
+
#
|
365
|
+
# @return [Integer] 0
|
366
|
+
def reset_id
|
367
|
+
@id = 0
|
368
|
+
end
|
369
|
+
|
370
|
+
# Checkes wether a transaction is mined or not.
|
371
|
+
#
|
372
|
+
# @param hash [String] the transaction hash.
|
373
|
+
# @return [Boolean] true if included in a block.
|
374
|
+
def is_mined_tx?(hash)
|
375
|
+
mined_tx = eth_get_transaction_by_hash hash
|
376
|
+
!mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
|
377
|
+
end
|
378
|
+
|
379
|
+
# Waits for an transaction to be mined by the connected chain.
|
380
|
+
#
|
381
|
+
# @param hash [String] the transaction hash.
|
382
|
+
# @return [String] the transaction hash once the transaction is mined.
|
383
|
+
# @raise [Timeout::Error] if it's not mined within 5 minutes.
|
384
|
+
def wait_for_tx(hash)
|
385
|
+
start_time = Time.now
|
386
|
+
timeout = 300
|
387
|
+
retry_rate = 0.1
|
388
|
+
loop do
|
389
|
+
raise Timeout::Error if ((Time.now - start_time) > timeout)
|
390
|
+
return hash if is_mined_tx? hash
|
391
|
+
sleep retry_rate
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Metafunction to provide all known RPC commands defined in
|
396
|
+
# Eth::Api as snake_case methods to the Eth::Client classes.
|
397
|
+
Api::COMMANDS.each do |cmd|
|
398
|
+
method_name = cmd.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
399
|
+
define_method method_name do |*args|
|
400
|
+
send_command cmd, args
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
# Non-transactional function call called from call().
|
407
|
+
def call_raw(contract, func, *args, **kwargs)
|
408
|
+
gas_limit = if kwargs[:gas_limit]
|
409
|
+
kwargs[:gas_limit]
|
410
|
+
else
|
411
|
+
Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
412
|
+
end
|
413
|
+
params = {
|
414
|
+
gas_limit: gas_limit,
|
415
|
+
chain_id: chain_id,
|
416
|
+
data: call_payload(func, args),
|
417
|
+
}
|
418
|
+
if kwargs[:address] || contract.address
|
419
|
+
params.merge!({ to: kwargs[:address] || contract.address })
|
420
|
+
end
|
421
|
+
if kwargs[:legacy]
|
422
|
+
params.merge!({
|
423
|
+
gas_price: max_fee_per_gas,
|
424
|
+
})
|
425
|
+
else
|
426
|
+
params.merge!({
|
427
|
+
priority_fee: max_priority_fee_per_gas,
|
428
|
+
max_gas_fee: max_fee_per_gas,
|
429
|
+
})
|
430
|
+
end
|
431
|
+
unless kwargs[:sender_key].nil?
|
432
|
+
# Uses the provided key as sender and signer
|
433
|
+
params.merge!({
|
434
|
+
from: kwargs[:sender_key].address,
|
435
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
436
|
+
})
|
437
|
+
tx = Eth::Tx.new(params)
|
438
|
+
tx.sign kwargs[:sender_key]
|
439
|
+
end
|
440
|
+
raw_result = eth_call(params)["result"]
|
441
|
+
types = func.outputs.map { |i| i.type }
|
442
|
+
return nil if raw_result == "0x"
|
443
|
+
Eth::Abi.decode(types, raw_result)
|
444
|
+
end
|
445
|
+
|
446
|
+
# Encodes function call payloads.
|
447
|
+
def call_payload(fun, args)
|
448
|
+
types = fun.inputs.map { |i| i.type }
|
449
|
+
encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
|
450
|
+
"0x" + fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)
|
451
|
+
end
|
452
|
+
|
453
|
+
# Encodes constructor params
|
454
|
+
def encode_constructor_params(contract, args)
|
455
|
+
types = contract.constructor_inputs.map { |input| input.type }
|
456
|
+
Util.bin_to_hex(Eth::Abi.encode(types, args))
|
457
|
+
end
|
458
|
+
|
459
|
+
# Prepares parameters and sends the command to the client.
|
460
|
+
def send_command(command, args)
|
461
|
+
args << "latest" if ["eth_getBalance", "eth_call"].include? command
|
462
|
+
payload = {
|
463
|
+
jsonrpc: "2.0",
|
464
|
+
method: command,
|
465
|
+
params: marshal(args),
|
466
|
+
id: next_id,
|
467
|
+
}
|
468
|
+
output = JSON.parse(send(payload.to_json))
|
469
|
+
raise IOError, output["error"]["message"] unless output["error"].nil?
|
470
|
+
return output
|
471
|
+
end
|
472
|
+
|
473
|
+
# Increments the request id.
|
474
|
+
def next_id
|
475
|
+
@id += 1
|
476
|
+
end
|
477
|
+
|
478
|
+
# Recursively marshals all request parameters.
|
479
|
+
def marshal(params)
|
480
|
+
if params.is_a? Array
|
481
|
+
return params.map! { |param| marshal(param) }
|
482
|
+
elsif params.is_a? Hash
|
483
|
+
return params.transform_values! { |param| marshal(param) }
|
484
|
+
elsif params.is_a? Numeric
|
485
|
+
return Util.prefix_hex "#{params.to_i.to_s(16)}"
|
486
|
+
elsif params.is_a? Address
|
487
|
+
return params.to_s
|
488
|
+
elsif Util.is_hex? params
|
489
|
+
return Util.prefix_hex params
|
490
|
+
else
|
491
|
+
return params
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# Load the client/* libraries
|
498
|
+
require "eth/client/http"
|
499
|
+
require "eth/client/ipc"
|
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,42 @@
|
|
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
|
+
# Provide classes for contract event.
|
21
|
+
class Contract::Event
|
22
|
+
attr_accessor :name, :signature, :input_types, :inputs, :event_string, :address
|
23
|
+
|
24
|
+
# Constructor of the {Eth::Contract::Event} class.
|
25
|
+
#
|
26
|
+
# @param data [Hash] contract event data.
|
27
|
+
def initialize(data)
|
28
|
+
@name = data["name"]
|
29
|
+
@input_types = data["inputs"].collect { |x| x["type"] }
|
30
|
+
@inputs = data["inputs"].collect { |x| x["name"] }
|
31
|
+
@event_string = "#{@name}(#{@input_types.join(",")})"
|
32
|
+
@signature = Digest::Keccak.hexdigest(@event_string, 256)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the address of the smart contract
|
36
|
+
#
|
37
|
+
# @param address [String] contract address.
|
38
|
+
def set_address(address)
|
39
|
+
@address = address.nil? ? nil : Eth::Address.new(address).address
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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 the methods for smart contract function.
|
21
|
+
class Contract::Function
|
22
|
+
attr_accessor :name, :inputs, :outputs, :signature, :constant, :function_string
|
23
|
+
|
24
|
+
# Constructor of the {Eth::Function} class.
|
25
|
+
#
|
26
|
+
# @param data [Hash] function input and output data.
|
27
|
+
def initialize(data)
|
28
|
+
@name = data["name"]
|
29
|
+
@constant = data["constant"]
|
30
|
+
@inputs = data["inputs"].map do |input|
|
31
|
+
Eth::Contract::FunctionInput.new(input)
|
32
|
+
end
|
33
|
+
@outputs = data["outputs"].collect do |output|
|
34
|
+
Eth::Contract::FunctionOutput.new(output)
|
35
|
+
end
|
36
|
+
@function_string = self.class.calc_signature(@name, @inputs)
|
37
|
+
@signature = self.class.encoded_function_signature(@function_string)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates function strings.
|
41
|
+
#
|
42
|
+
# @param name [String] function name.
|
43
|
+
# @param inputs [Array<Eth::Contract::FunctionInput>] function input class list.
|
44
|
+
# @return [String] function string.
|
45
|
+
def self.calc_signature(name, inputs)
|
46
|
+
"#{name}(#{inputs.collect { |x| x.raw_type }.join(",")})"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Encodes a function signature.
|
50
|
+
#
|
51
|
+
# @param signature [String] function signature.
|
52
|
+
# @return [String] encoded function signature string.
|
53
|
+
def self.encoded_function_signature(signature)
|
54
|
+
Util.bin_to_hex Util.keccak256(signature)[0..3]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|