eth 0.5.1 → 0.5.4
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/spec.yml +3 -3
- data/AUTHORS.txt +10 -2
- data/CHANGELOG.md +29 -6
- data/README.md +94 -23
- data/codecov.yml +6 -0
- data/eth.gemspec +1 -1
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +1 -1
- data/lib/eth/abi.rb +14 -2
- data/lib/eth/chain.rb +1 -1
- data/lib/eth/client/ipc.rb +3 -0
- data/lib/eth/client.rb +240 -1
- 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 +135 -0
- data/lib/eth/rlp/decoder.rb +7 -2
- data/lib/eth/rlp/sedes/binary.rb +2 -2
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +1 -0
- data/lib/eth/tx/eip2930.rb +1 -0
- data/lib/eth/tx/legacy.rb +1 -0
- data/lib/eth/tx.rb +24 -29
- data/lib/eth/version.rb +1 -1
- data/lib/eth.rb +7 -0
- metadata +20 -5
data/lib/eth/client.rb
CHANGED
@@ -149,6 +149,203 @@ module Eth
|
|
149
149
|
end
|
150
150
|
end
|
151
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, sender_key)
|
158
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
159
|
+
# @param sender_key [Eth::Key] the sender private key.
|
160
|
+
# @overload deploy(contract, sender_key, legacy)
|
161
|
+
# @param contract [Eth::Contract] contracts to deploy.
|
162
|
+
# @param sender_key [Eth::Key] the sender private key.
|
163
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
164
|
+
# @return [String] the contract address.
|
165
|
+
def deploy_and_wait(contract, sender_key: nil, legacy: false)
|
166
|
+
hash = wait_for_tx(deploy(contract, sender_key: sender_key, legacy: legacy))
|
167
|
+
addr = eth_get_transaction_receipt(hash)["result"]["contractAddress"]
|
168
|
+
contract.address = Address.new(addr).to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
# Deploys a 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
|
+
# @raise [ArgumentError] in case the contract does not have any source.
|
185
|
+
def deploy(contract, sender_key: nil, legacy: false)
|
186
|
+
raise ArgumentError, "Cannot deploy contract without source or binary!" if contract.bin.nil?
|
187
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
188
|
+
params = {
|
189
|
+
value: 0,
|
190
|
+
gas_limit: gas_limit,
|
191
|
+
chain_id: chain_id,
|
192
|
+
data: contract.bin,
|
193
|
+
}
|
194
|
+
if legacy
|
195
|
+
params.merge!({
|
196
|
+
gas_price: max_fee_per_gas,
|
197
|
+
})
|
198
|
+
else
|
199
|
+
params.merge!({
|
200
|
+
priority_fee: max_priority_fee_per_gas,
|
201
|
+
max_gas_fee: max_fee_per_gas,
|
202
|
+
})
|
203
|
+
end
|
204
|
+
unless sender_key.nil?
|
205
|
+
# use the provided key as sender and signer
|
206
|
+
params.merge!({
|
207
|
+
from: sender_key.address,
|
208
|
+
nonce: get_nonce(sender_key.address),
|
209
|
+
})
|
210
|
+
tx = Eth::Tx.new(params)
|
211
|
+
tx.sign sender_key
|
212
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
213
|
+
else
|
214
|
+
# use the default account as sender and external signer
|
215
|
+
params.merge!({
|
216
|
+
from: default_account,
|
217
|
+
nonce: get_nonce(default_account),
|
218
|
+
})
|
219
|
+
return eth_send_transaction(params)["result"]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Calls a contract function without executing it
|
224
|
+
# (non-transactional contract read).
|
225
|
+
#
|
226
|
+
# @overload call(contract, function_name)
|
227
|
+
# @param contract [Eth::Contract] subject contract to call.
|
228
|
+
# @param function_name [String] method name to be called.
|
229
|
+
# @overload call(contract, function_name, value)
|
230
|
+
# @param contract [Eth::Contract] subject contract to call.
|
231
|
+
# @param function_name [String] method name to be called.
|
232
|
+
# @param value [Integer|String] function arguments.
|
233
|
+
# @overload call(contract, function_name, value, sender_key, legacy)
|
234
|
+
# @param contract [Eth::Contract] subject contract to call.
|
235
|
+
# @param function_name [String] method name to be called.
|
236
|
+
# @param value [Integer|String] function arguments.
|
237
|
+
# @param sender_key [Eth::Key] the sender private key.
|
238
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
239
|
+
# @return [Object] returns the result of the call.
|
240
|
+
def call(contract, function_name, *args, **kwargs)
|
241
|
+
func = contract.functions.select { |func| func.name == function_name }[0]
|
242
|
+
raise ArgumentError, "function_name does not exist!" if func.nil?
|
243
|
+
output = call_raw(contract, func, *args, **kwargs)
|
244
|
+
if output.length == 1
|
245
|
+
return output[0]
|
246
|
+
else
|
247
|
+
return output
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Executes a contract function with a transaction (transactional
|
252
|
+
# contract read/write).
|
253
|
+
#
|
254
|
+
# @overload transact(contract, function_name)
|
255
|
+
# @param contract [Eth::Contract] subject contract to call.
|
256
|
+
# @param function_name [String] method name to be called.
|
257
|
+
# @overload transact(contract, function_name, value)
|
258
|
+
# @param contract [Eth::Contract] subject contract to call.
|
259
|
+
# @param function_name [String] method name to be called.
|
260
|
+
# @param value [Integer|String] function arguments.
|
261
|
+
# @overload transact(contract, function_name, value, sender_key, legacy, address)
|
262
|
+
# @param contract [Eth::Contract] subject contract to call.
|
263
|
+
# @param function_name [String] method name to be called.
|
264
|
+
# @param value [Integer|String] function arguments.
|
265
|
+
# @param sender_key [Eth::Key] the sender private key.
|
266
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
267
|
+
# @param address [String] contract address.
|
268
|
+
# @return [Object] returns the result of the call.
|
269
|
+
def transact(contract, function_name, *args, **kwargs)
|
270
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
271
|
+
fun = contract.functions.select { |func| func.name == function_name }[0]
|
272
|
+
params = {
|
273
|
+
value: 0,
|
274
|
+
gas_limit: gas_limit,
|
275
|
+
chain_id: chain_id,
|
276
|
+
to: kwargs[:address] || contract.address,
|
277
|
+
data: call_payload(fun, args),
|
278
|
+
}
|
279
|
+
if kwargs[:legacy]
|
280
|
+
params.merge!({
|
281
|
+
gas_price: max_fee_per_gas,
|
282
|
+
})
|
283
|
+
else
|
284
|
+
params.merge!({
|
285
|
+
priority_fee: max_priority_fee_per_gas,
|
286
|
+
max_gas_fee: max_fee_per_gas,
|
287
|
+
})
|
288
|
+
end
|
289
|
+
unless kwargs[:sender_key].nil?
|
290
|
+
# use the provided key as sender and signer
|
291
|
+
params.merge!({
|
292
|
+
from: kwargs[:sender_key].address,
|
293
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
294
|
+
})
|
295
|
+
tx = Eth::Tx.new(params)
|
296
|
+
tx.sign kwargs[:sender_key]
|
297
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
298
|
+
else
|
299
|
+
# use the default account as sender and external signer
|
300
|
+
params.merge!({
|
301
|
+
from: default_account,
|
302
|
+
nonce: get_nonce(default_account),
|
303
|
+
})
|
304
|
+
return eth_send_transaction(params)["result"]
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Executes a contract function with a transaction and waits for it
|
309
|
+
# to be mined (transactional contract read/write).
|
310
|
+
#
|
311
|
+
# @overload transact_and_wait(contract, function_name)
|
312
|
+
# @param contract [Eth::Contract] subject contract to call.
|
313
|
+
# @param function_name [String] method name to be called.
|
314
|
+
# @overload transact_and_wait(contract, function_name, value)
|
315
|
+
# @param contract [Eth::Contract] subject contract to call.
|
316
|
+
# @param function_name [String] method name to be called.
|
317
|
+
# @param value [Integer|String] function arguments.
|
318
|
+
# @overload transact_and_wait(contract, function_name, value, sender_key, legacy, address)
|
319
|
+
# @param contract [Eth::Contract] subject contract to call.
|
320
|
+
# @param function_name [String] method name to be called.
|
321
|
+
# @param value [Integer|String] function arguments.
|
322
|
+
# @param sender_key [Eth::Key] the sender private key.
|
323
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
324
|
+
# @param address [String] contract address.
|
325
|
+
# @return [Object] returns the result of the call.
|
326
|
+
def transact_and_wait(contract, function_name, *args, **kwargs)
|
327
|
+
wait_for_tx(transact(contract, function_name, *args, **kwargs))
|
328
|
+
end
|
329
|
+
|
330
|
+
# Provides an interface to call `isValidSignature` as per EIP-1271 on a given
|
331
|
+
# smart contract to verify the given hash and signature matching the magic
|
332
|
+
# value.
|
333
|
+
#
|
334
|
+
# @param contract [Eth::Contract] a deployed contract implementing EIP-1271.
|
335
|
+
# @param hash [String] the message hash to be checked against the signature.
|
336
|
+
# @param signature [String] the signature to be recovered by the contract.
|
337
|
+
# @param magic [String] the expected magic value (defaults to `1626ba7e`).
|
338
|
+
# @return [Boolean] true if magic matches and signature is valid.
|
339
|
+
# @raise [ArgumentError] in case the contract cannot be called yet.
|
340
|
+
def is_valid_signature(contract, hash, signature, magic = "1626ba7e")
|
341
|
+
raise ArgumentError, "Contract not deployed yet." if contract.address.nil?
|
342
|
+
hash = Util.hex_to_bin hash if Util.is_hex? hash
|
343
|
+
signature = Util.hex_to_bin signature if Util.is_hex? signature
|
344
|
+
magic = Util.hex_to_bin magic if Util.is_hex? magic
|
345
|
+
result = call(contract, "isValidSignature", hash, signature)
|
346
|
+
return result === magic
|
347
|
+
end
|
348
|
+
|
152
349
|
# Gives control over resetting the RPC request ID back to zero.
|
153
350
|
# Usually not needed.
|
154
351
|
#
|
@@ -169,7 +366,7 @@ module Eth
|
|
169
366
|
# Waits for an transaction to be mined by the connected chain.
|
170
367
|
#
|
171
368
|
# @param hash [String] the transaction hash.
|
172
|
-
# @return [String] the
|
369
|
+
# @return [String] the transaction hash once the transaction is mined.
|
173
370
|
# @raise [Timeout::Error] if it's not mined within 5 minutes.
|
174
371
|
def wait_for_tx(hash)
|
175
372
|
start_time = Time.now
|
@@ -193,6 +390,48 @@ module Eth
|
|
193
390
|
|
194
391
|
private
|
195
392
|
|
393
|
+
# Non-transactional function call called from call().
|
394
|
+
def call_raw(contract, func, *args, **kwargs)
|
395
|
+
gas_limit = Tx.estimate_intrinsic_gas(contract.bin) + Tx::CREATE_GAS
|
396
|
+
params = {
|
397
|
+
gas_limit: gas_limit,
|
398
|
+
chain_id: chain_id,
|
399
|
+
data: call_payload(func, args),
|
400
|
+
}
|
401
|
+
if kwargs[:address] || contract.address
|
402
|
+
params.merge!({ to: kwargs[:address] || contract.address })
|
403
|
+
end
|
404
|
+
if kwargs[:legacy]
|
405
|
+
params.merge!({
|
406
|
+
gas_price: max_fee_per_gas,
|
407
|
+
})
|
408
|
+
else
|
409
|
+
params.merge!({
|
410
|
+
priority_fee: max_priority_fee_per_gas,
|
411
|
+
max_gas_fee: max_fee_per_gas,
|
412
|
+
})
|
413
|
+
end
|
414
|
+
unless kwargs[:sender_key].nil?
|
415
|
+
# use the provided key as sender and signer
|
416
|
+
params.merge!({
|
417
|
+
from: kwargs[:sender_key].address,
|
418
|
+
nonce: get_nonce(kwargs[:sender_key].address),
|
419
|
+
})
|
420
|
+
tx = Eth::Tx.new(params)
|
421
|
+
tx.sign kwargs[:sender_key]
|
422
|
+
end
|
423
|
+
raw_result = eth_call(params)["result"]
|
424
|
+
types = func.outputs.map { |i| i.type }
|
425
|
+
Eth::Abi.decode(types, raw_result)
|
426
|
+
end
|
427
|
+
|
428
|
+
# Encodes function call payloads.
|
429
|
+
def call_payload(fun, args)
|
430
|
+
types = fun.inputs.map { |i| i.type }
|
431
|
+
encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
|
432
|
+
"0x" + fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)
|
433
|
+
end
|
434
|
+
|
196
435
|
# Prepares parameters and sends the command to the client.
|
197
436
|
def send_command(command, args)
|
198
437
|
args << "latest" if ["eth_getBalance", "eth_call"].include? command
|
@@ -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
|
@@ -0,0 +1,36 @@
|
|
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 function input.
|
20
|
+
class Contract::FunctionInput
|
21
|
+
attr_accessor :type, :name
|
22
|
+
|
23
|
+
# Constructor of the {Eth::Contract::FunctionInput} class.
|
24
|
+
#
|
25
|
+
# @param data [Hash] contract abi data.
|
26
|
+
def initialize(data)
|
27
|
+
@type = Eth::Abi::Type.parse(data["type"])
|
28
|
+
@name = data["name"]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns types like uint256
|
32
|
+
def type
|
33
|
+
@type.base_type + @type.sub_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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 function output.
|
20
|
+
class Contract::FunctionOutput
|
21
|
+
attr_accessor :type, :name
|
22
|
+
|
23
|
+
def initialize(data)
|
24
|
+
@type = Eth::Abi::Type.parse(data["type"])
|
25
|
+
@name = data["name"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
@type.base_type + @type.sub_type
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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 initializer.
|
20
|
+
class Contract::Initializer
|
21
|
+
attr_accessor :contracts, :file
|
22
|
+
|
23
|
+
# Constructor of the {Eth::Contract::Initializer} class.
|
24
|
+
#
|
25
|
+
# @param file [String] file path to solidity code.
|
26
|
+
def initialize(file)
|
27
|
+
sol_output = Eth::Solidity.new.compile(file)
|
28
|
+
contracts = sol_output.keys
|
29
|
+
|
30
|
+
@contracts = []
|
31
|
+
contracts.each do |contract|
|
32
|
+
abi = sol_output[contract]["abi"]
|
33
|
+
name = contract
|
34
|
+
code = sol_output[contract]["bin"]
|
35
|
+
@contracts << Contract.new(name, code, abi)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Build and return all contracts.
|
40
|
+
def build_all
|
41
|
+
@contracts.each do |contract|
|
42
|
+
contract.build
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/eth/contract.rb
ADDED
@@ -0,0 +1,135 @@
|
|
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 classes to access smart contracts
|
21
|
+
class Contract
|
22
|
+
attr_reader :address
|
23
|
+
attr_accessor :key
|
24
|
+
attr_accessor :gas_limit, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :nonce
|
25
|
+
attr_accessor :bin, :name, :abi, :class_object
|
26
|
+
attr_accessor :events, :functions, :constructor_inputs
|
27
|
+
|
28
|
+
# Constructor of the {Eth::Contract} class.
|
29
|
+
#
|
30
|
+
# @param name [String] contract name.
|
31
|
+
# @param bin [String] contract bin string.
|
32
|
+
# @param abi [String] contract abi string.
|
33
|
+
def initialize(name, bin, abi)
|
34
|
+
@name = name
|
35
|
+
@bin = bin
|
36
|
+
@abi = abi
|
37
|
+
@constructor_inputs, @functions, @events = parse_abi(abi)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a contract wrapper from a Solidity file.
|
41
|
+
#
|
42
|
+
# @param file [String] solidity file path.
|
43
|
+
# @param contract_index [Number] specify contract.
|
44
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
45
|
+
# @raise [ArgumentError] if the file path is empty or no contracts were compiled.
|
46
|
+
def self.from_file(file:, contract_index: 0)
|
47
|
+
raise ArgumentError, "Cannot find the contract at #{file.to_s}!" if !File.exist?(file.to_s)
|
48
|
+
contracts = Eth::Contract::Initializer.new(file).build_all
|
49
|
+
raise ArgumentError, "No contracts compiled." if contracts.empty?
|
50
|
+
contracts[contract_index].class_object.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a contract wrapper from ABI and address.
|
54
|
+
#
|
55
|
+
# @param abi [String] contract abi string.
|
56
|
+
# @param address [String] contract address.
|
57
|
+
# @param name [String] name of contract.
|
58
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
59
|
+
# @raise [JSON::ParserError] if the json format is wrong.
|
60
|
+
# @raise [ArgumentError] if ABI, address, or name is missing.
|
61
|
+
def self.from_abi(abi:, address:, name:)
|
62
|
+
abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
|
63
|
+
contract = Eth::Contract.new(name, nil, abi)
|
64
|
+
contract.build
|
65
|
+
contract = contract.class_object.new
|
66
|
+
contract.address = address
|
67
|
+
contract
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a contract wrapper from binary and ABI.
|
71
|
+
#
|
72
|
+
# @param bin [String] contract bin string.
|
73
|
+
# @param abi [String] contract abi string.
|
74
|
+
# @param name [String] name of contract.
|
75
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
76
|
+
# @raise [JSON::ParserError] if the json format is wrong.
|
77
|
+
# @raise [ArgumentError] if ABI, binary, or name is missing.
|
78
|
+
def self.from_bin(bin:, abi:, name:)
|
79
|
+
abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
|
80
|
+
contract = Eth::Contract.new(name, bin, abi)
|
81
|
+
contract.build
|
82
|
+
contract.class_object.new
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the address of the smart contract.
|
86
|
+
#
|
87
|
+
# @param addr [String|Eth::Address] contract address string.
|
88
|
+
def address=(addr)
|
89
|
+
if addr.is_a? Eth::Address
|
90
|
+
@address = addr.to_s
|
91
|
+
else
|
92
|
+
@address = Eth::Address.new(addr).to_s
|
93
|
+
end
|
94
|
+
@events.each do |event|
|
95
|
+
event.set_address(@address)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Create meta classes for smart contracts.
|
100
|
+
def build
|
101
|
+
class_name = @name
|
102
|
+
parent = self
|
103
|
+
class_methods = Class.new do
|
104
|
+
extend Forwardable
|
105
|
+
def_delegators :parent, :key, :key=
|
106
|
+
def_delegators :parent, :name, :abi, :bin
|
107
|
+
def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
|
108
|
+
def_delegators :parent, :max_fee_per_gas, :max_fee_per_gas=, :max_priority_fee_per_gas, :max_priority_fee_per_gas=
|
109
|
+
def_delegators :parent, :events
|
110
|
+
def_delegators :parent, :address, :address=
|
111
|
+
def_delegator :parent, :functions
|
112
|
+
define_method :parent do
|
113
|
+
parent
|
114
|
+
end
|
115
|
+
end
|
116
|
+
Eth::Contract.send(:remove_const, class_name) if Eth::Contract.const_defined?(class_name, false)
|
117
|
+
Eth::Contract.const_set(class_name, class_methods)
|
118
|
+
@class_object = class_methods
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def parse_abi(abi)
|
124
|
+
constructor = abi.detect { |x| x["type"] == "constructor" }
|
125
|
+
if !constructor.nil?
|
126
|
+
constructor_inputs = constructor["inputs"].map { |input| Eth::Contract::FunctionInput.new(input) }
|
127
|
+
else
|
128
|
+
constructor_inputs = []
|
129
|
+
end
|
130
|
+
functions = abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) }
|
131
|
+
events = abi.select { |x| x["type"] == "event" }.map { |evt| Eth::Contract::Event.new(evt) }
|
132
|
+
[constructor_inputs, functions, events]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/eth/rlp/decoder.rb
CHANGED
@@ -63,7 +63,7 @@ module Eth
|
|
63
63
|
# short string
|
64
64
|
[:str, b0 - Constant::PRIMITIVE_PREFIX_OFFSET, start + 1]
|
65
65
|
elsif b0 < Constant::LIST_PREFIX_OFFSET
|
66
|
-
|
66
|
+
enforce_no_zero_bytes rlp, start
|
67
67
|
|
68
68
|
# long string
|
69
69
|
ll = b0 - Constant::PRIMITIVE_PREFIX_OFFSET - Constant::SHORT_LENGTH_LIMIT + 1
|
@@ -75,7 +75,7 @@ module Eth
|
|
75
75
|
# short list
|
76
76
|
[:list, b0 - Constant::LIST_PREFIX_OFFSET, start + 1]
|
77
77
|
else
|
78
|
-
|
78
|
+
enforce_no_zero_bytes rlp, start
|
79
79
|
|
80
80
|
# long list
|
81
81
|
ll = b0 - Constant::LIST_PREFIX_OFFSET - Constant::SHORT_LENGTH_LIMIT + 1
|
@@ -85,6 +85,11 @@ module Eth
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
+
# Enforce RLP slices to not start with empty bytes.
|
89
|
+
def enforce_no_zero_bytes(rlp, start)
|
90
|
+
raise DecodingError, "Length starts with zero bytes" if rlp.slice(start + 1) == Constant::BYTE_ZERO
|
91
|
+
end
|
92
|
+
|
88
93
|
# Consume an RLP payload at the given position of given type and size.
|
89
94
|
def consume_payload(rlp, start, type, length)
|
90
95
|
case type
|
data/lib/eth/rlp/sedes/binary.rb
CHANGED
@@ -29,7 +29,7 @@ module Eth
|
|
29
29
|
# A singleton class for binary values of fixed length.
|
30
30
|
class << self
|
31
31
|
|
32
|
-
# Create a serializable
|
32
|
+
# Create a serializable binary of fixed size.
|
33
33
|
#
|
34
34
|
# @param l [Integer] the fixed size of the binary.
|
35
35
|
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
@@ -47,7 +47,7 @@ module Eth
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
# Create a serializable
|
50
|
+
# Create a serializable binary of variable size.
|
51
51
|
#
|
52
52
|
# @param min_length [Integer] the minimum size of the binary.
|
53
53
|
# @param max_length [Integer] the maximum size of the binary.
|