etherlite 0.1.4 → 0.1.5
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/README.md +92 -7
- data/etherlite.gemspec +2 -0
- data/lib/etherlite.rb +12 -7
- data/lib/etherlite/abi.rb +44 -4
- data/lib/etherlite/account/anonymous.rb +9 -0
- data/lib/etherlite/account/base.rb +56 -0
- data/lib/etherlite/account/local.rb +40 -0
- data/lib/etherlite/account/private_key.rb +51 -0
- data/lib/etherlite/api/node.rb +15 -12
- data/lib/etherlite/api/rpc.rb +55 -0
- data/lib/etherlite/commands/abi/load_contract.rb +24 -10
- data/lib/etherlite/commands/abi/load_function.rb +5 -5
- data/lib/etherlite/commands/abi/load_type.rb +1 -1
- data/lib/etherlite/commands/contract/event_base/decode_log_inputs.rb +30 -10
- data/lib/etherlite/configuration.rb +2 -1
- data/lib/etherlite/connection.rb +8 -4
- data/lib/etherlite/contract/base.rb +53 -1
- data/lib/etherlite/contract/function.rb +18 -14
- data/lib/etherlite/errors.rb +27 -0
- data/lib/etherlite/nonce_manager.rb +13 -14
- data/lib/etherlite/support/array.rb +43 -0
- data/lib/etherlite/transaction.rb +33 -1
- data/lib/etherlite/types/array_dynamic.rb +19 -5
- data/lib/etherlite/types/array_fixed.rb +19 -10
- data/lib/etherlite/types/{bool.rb → boolean.rb} +1 -1
- data/lib/etherlite/types/byte_string.rb +6 -0
- data/lib/etherlite/types/string.rb +6 -1
- data/lib/etherlite/utils.rb +4 -2
- data/lib/etherlite/version.rb +1 -1
- data/lib/generators/etherlite/templates/etherlite.yml +1 -0
- metadata +38 -8
- data/lib/etherlite/account.rb +0 -85
- data/lib/etherlite/commands/base.rb +0 -0
- data/lib/etherlite/commands/contract/function/encode_arguments.rb +0 -32
- data/lib/etherlite/pk_account.rb +0 -82
- data/lib/etherlite/types/array_base.rb +0 -36
@@ -0,0 +1,55 @@
|
|
1
|
+
module Etherlite
|
2
|
+
module Api
|
3
|
+
module Rpc
|
4
|
+
def eth_block_number
|
5
|
+
Etherlite::Utils.hex_to_uint ipc_call(:eth_blockNumber)
|
6
|
+
end
|
7
|
+
|
8
|
+
def eth_gas_price
|
9
|
+
Etherlite::Utils.hex_to_uint ipc_call(:eth_gasPrice)
|
10
|
+
end
|
11
|
+
|
12
|
+
def eth_get_transaction_receipt(_tx_hash)
|
13
|
+
ipc_call(:eth_getTransactionReceipt, _tx_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def eth_get_transaction_count(_address, _block = 'latest')
|
17
|
+
Etherlite::Utils.hex_to_uint ipc_call(:eth_getTransactionCount, _address, _block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def eth_call(_params, _block = 'latest')
|
21
|
+
ipc_call(:eth_call, _params, _block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def eth_send_raw_transaction(_hex_data)
|
25
|
+
ipc_call(:eth_sendRawTransaction, _hex_data)
|
26
|
+
end
|
27
|
+
|
28
|
+
def eth_send_transaction(_params)
|
29
|
+
ipc_call(:eth_sendTransaction, _params)
|
30
|
+
end
|
31
|
+
|
32
|
+
def personal_send_transaction(_params, _passphrase)
|
33
|
+
ipc_call(:personal_sendTransaction, _params, _passphrase)
|
34
|
+
end
|
35
|
+
|
36
|
+
# TestRPC support
|
37
|
+
|
38
|
+
def evm_snapshot
|
39
|
+
ipc_call(:evm_snapshot)
|
40
|
+
end
|
41
|
+
|
42
|
+
def evm_revert(_snapshot_id)
|
43
|
+
ipc_call(:evm_revert, _snapshot_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def evm_increase_time(_seconds)
|
47
|
+
Etherlite::Utils.hex_to_uint ipc_call(:evm_increase_time, _seconds)
|
48
|
+
end
|
49
|
+
|
50
|
+
def evm_mine
|
51
|
+
ipc_call(:evm_mine)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module Etherlite::Abi
|
2
|
-
class LoadContract < PowerTypes::Command.new(:
|
2
|
+
class LoadContract < PowerTypes::Command.new(:artifact)
|
3
3
|
def perform
|
4
4
|
klass = Class.new(Etherlite::Contract::Base)
|
5
5
|
|
6
|
+
define_class_methods klass
|
7
|
+
|
6
8
|
abi_definitions.each do |definition|
|
7
9
|
case definition['type']
|
8
10
|
when 'function'
|
@@ -20,21 +22,15 @@ module Etherlite::Abi
|
|
20
22
|
private
|
21
23
|
|
22
24
|
def abi_definitions
|
23
|
-
@
|
25
|
+
@artifact['abi'] || []
|
24
26
|
end
|
25
27
|
|
26
28
|
def define_function(_class, _definition)
|
27
|
-
|
28
|
-
function = Etherlite::Contract::Function.new(
|
29
|
-
_definition['name'],
|
30
|
-
function_args,
|
31
|
-
payable: _definition['payable'],
|
32
|
-
constant: _definition['constant']
|
33
|
-
)
|
29
|
+
function = build_function_from_definition _definition
|
34
30
|
|
35
31
|
_class.functions << function
|
36
32
|
_class.class_eval do
|
37
|
-
define_method(
|
33
|
+
define_method(function.name.underscore) do |*params|
|
38
34
|
account = (params.last.is_a?(Hash) && params.last[:as]) || default_account
|
39
35
|
|
40
36
|
raise ArgumentError, 'must provide a source account' if account.nil?
|
@@ -62,5 +58,23 @@ module Etherlite::Abi
|
|
62
58
|
_class.events << event_class
|
63
59
|
_class.const_set(_definition['name'], event_class)
|
64
60
|
end
|
61
|
+
|
62
|
+
def define_class_methods(_class)
|
63
|
+
unlinked_binary = @artifact['unlinked_binary'] || ''
|
64
|
+
|
65
|
+
_class.class_eval do
|
66
|
+
define_singleton_method('unlinked_binary') { unlinked_binary }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_function_from_definition(_definition)
|
71
|
+
Etherlite::Contract::Function.new(
|
72
|
+
_definition['name'],
|
73
|
+
_definition['inputs'].map { |input| LoadType.for signature: input['type'] },
|
74
|
+
_definition['outputs'].map { |input| LoadType.for signature: input['type'] },
|
75
|
+
_definition['payable'],
|
76
|
+
_definition['constant']
|
77
|
+
)
|
78
|
+
end
|
65
79
|
end
|
66
80
|
end
|
@@ -6,16 +6,16 @@ module Etherlite::Abi
|
|
6
6
|
parts = MATCHER.match @signature
|
7
7
|
raise ArgumentError, 'invalid method signature' if parts.nil?
|
8
8
|
|
9
|
-
|
9
|
+
inputs = parts[3].split(',').map { |a| LoadType.for(signature: a.strip) }
|
10
10
|
|
11
11
|
case parts[1]
|
12
12
|
when 'payable'
|
13
|
-
build parts[2],
|
13
|
+
build parts[2], inputs, [], true, false
|
14
14
|
when 'onchain'
|
15
|
-
build parts[2],
|
15
|
+
build parts[2], inputs, [], false, false
|
16
16
|
else
|
17
|
-
|
18
|
-
build parts[2],
|
17
|
+
ouputs = parts[1] == 'void' ? [] : [LoadType.for(signature: parts[1])]
|
18
|
+
build parts[2], inputs, ouputs, false, true
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -20,7 +20,7 @@ module Etherlite::Abi
|
|
20
20
|
when 'fixed' then Etherlite::Types::Fixed.new(true, b1_128(_parts), b2_128(_parts))
|
21
21
|
when 'string' then Etherlite::Types::String.new
|
22
22
|
when 'address' then Etherlite::Types::Address.new
|
23
|
-
when 'bool' then Etherlite::Types::
|
23
|
+
when 'bool' then Etherlite::Types::Boolean.new
|
24
24
|
when 'bytes'
|
25
25
|
if _parts[:b1].present?
|
26
26
|
Etherlite::Types::Bytes.new(_parts[:b1].to_i)
|
@@ -1,21 +1,41 @@
|
|
1
1
|
class Etherlite::Contract::EventBase
|
2
2
|
class DecodeLogInputs < PowerTypes::Command.new(:connection, :inputs, :json)
|
3
3
|
def perform
|
4
|
-
|
5
|
-
|
6
|
-
attributes
|
4
|
+
store_by_name non_indexed_inputs, non_indexed_values
|
5
|
+
store_by_name indexed_inputs, indexed_values
|
6
|
+
attributes
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
+
private
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def attributes
|
12
|
+
@attributes ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def non_indexed_inputs
|
16
|
+
@non_indexed_inputs ||= @inputs.select { |i| !i.indexed? }
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
19
|
+
def non_indexed_values
|
20
|
+
Etherlite::Support::Array.decode(
|
21
|
+
@connection, non_indexed_inputs.map(&:type), @json['data'][2..-1]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def indexed_inputs
|
26
|
+
@indexed_inputs ||= @inputs.select(&:indexed?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def indexed_values
|
30
|
+
topics = @json['topics'][1..-1]
|
31
|
+
indexed_inputs.each_with_index.map do |input, i|
|
32
|
+
# dynamic indexed inputs cannot be retrieved (only the hash is stored)
|
33
|
+
input.type.dynamic? ? topics[i] : input.type.decode(@connection, topics[i])
|
16
34
|
end
|
35
|
+
end
|
17
36
|
|
18
|
-
|
37
|
+
def store_by_name(_inputs, _values)
|
38
|
+
_inputs.each_with_index { |input, i| attributes[input.original_name] = _values[i] }
|
19
39
|
end
|
20
40
|
end
|
21
41
|
end
|
@@ -2,10 +2,11 @@ module Etherlite
|
|
2
2
|
class Configuration
|
3
3
|
DEFAULTS = {
|
4
4
|
url: 'http://127.0.0.1:8545',
|
5
|
+
chain_id: nil, # any chain
|
5
6
|
logger: nil # set by method
|
6
7
|
}
|
7
8
|
|
8
|
-
attr_accessor :url, :logger
|
9
|
+
attr_accessor :url, :chain_id, :logger
|
9
10
|
|
10
11
|
def initialize
|
11
12
|
assign_attributes DEFAULTS
|
data/lib/etherlite/connection.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
module Etherlite
|
2
2
|
class Connection
|
3
|
-
|
3
|
+
include Api::Rpc
|
4
|
+
|
5
|
+
attr_reader :chain_id
|
6
|
+
|
7
|
+
def initialize(_uri, _chain_id = nil)
|
4
8
|
@uri = _uri
|
9
|
+
@chain_id = _chain_id
|
5
10
|
end
|
6
11
|
|
7
12
|
def ipc_call(_method, *_params)
|
@@ -31,12 +36,11 @@ module Etherlite
|
|
31
36
|
def handle_response(_response, _id)
|
32
37
|
case _response
|
33
38
|
when Net::HTTPSuccess
|
34
|
-
# puts _response.body
|
35
39
|
json_body = JSON.parse _response.body
|
36
|
-
|
40
|
+
raise NodeError.new json_body['error'] if json_body['error']
|
37
41
|
json_body['result']
|
38
42
|
else
|
39
|
-
raise _response
|
43
|
+
raise RPCError.new _response.code, _response.body
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -1,15 +1,62 @@
|
|
1
1
|
module Etherlite::Contract
|
2
|
+
##
|
3
|
+
# The base class for etherlite contract classes
|
4
|
+
#
|
2
5
|
class Base
|
3
6
|
include Etherlite::Api::Address
|
4
7
|
|
8
|
+
# The contract's registered functions
|
5
9
|
def self.functions
|
6
10
|
@functions ||= []
|
7
11
|
end
|
8
12
|
|
13
|
+
# The contract's registered events
|
9
14
|
def self.events
|
10
15
|
@events ||= []
|
11
16
|
end
|
12
17
|
|
18
|
+
# The contract's compiled bytecode in binary format (stored as hex)
|
19
|
+
def self.binary
|
20
|
+
@binary ||= begin
|
21
|
+
if /__[^_]+_+/ === unlinked_binary
|
22
|
+
raise UnlinkedContractError, 'compiled contract contains unresolved library references'
|
23
|
+
end
|
24
|
+
|
25
|
+
unlinked_binary
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Deploys the contract and waits for the creation transaction to be mined.
|
31
|
+
#
|
32
|
+
# This method can be given a source account or client. If an account is given, the it will be
|
33
|
+
# used to send the creation transaction. If instead a client is given, the client
|
34
|
+
# `default_account` will be used to send the transaction. If no account nor client is given,
|
35
|
+
# then the default_account from the default client will be used (if configured).
|
36
|
+
#
|
37
|
+
# @param as (Object) The source account
|
38
|
+
# @param client (Object) The source client (no effect if :as is given)
|
39
|
+
# @param timeout (Integer) The transaction mining timeout in seconds, defaults to 120 seconds.
|
40
|
+
#
|
41
|
+
# This method also takes any parameters the underlying account.send_transaction accepts, like
|
42
|
+
# :gas, :gas_price, etc.
|
43
|
+
#
|
44
|
+
# @return [Object] instance of the contract class pointing the newly deployed contract address.
|
45
|
+
#
|
46
|
+
def self.deploy(_options = {})
|
47
|
+
as = _options[:as] || _options[:client].try(:default_account) || Etherlite.default_account
|
48
|
+
|
49
|
+
tx = as.send_transaction({ data: binary }.merge(_options))
|
50
|
+
if tx.wait_for_block(timeout: _options.fetch(:timeout, 120))
|
51
|
+
at tx.contract_address, as: as
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Creates a new instance of the contract class that points to a given address.
|
57
|
+
#
|
58
|
+
#
|
59
|
+
#
|
13
60
|
def self.at(_address, client: nil, as: nil)
|
14
61
|
_address = Etherlite::Utils.normalize_address_param _address
|
15
62
|
|
@@ -17,7 +64,7 @@ module Etherlite::Contract
|
|
17
64
|
new(as.connection, _address, as)
|
18
65
|
else
|
19
66
|
client ||= ::Etherlite
|
20
|
-
new(client.connection, _address, client.
|
67
|
+
new(client.connection, _address, client.default_account)
|
21
68
|
end
|
22
69
|
end
|
23
70
|
|
@@ -29,6 +76,11 @@ module Etherlite::Contract
|
|
29
76
|
@default_account = _default_account
|
30
77
|
end
|
31
78
|
|
79
|
+
##
|
80
|
+
# Searches for event logs emitted by this contract
|
81
|
+
#
|
82
|
+
#
|
83
|
+
#
|
32
84
|
def get_logs(events: nil, from_block: :earliest, to_block: :latest)
|
33
85
|
params = {
|
34
86
|
address: json_encoded_address,
|
@@ -1,16 +1,14 @@
|
|
1
|
-
require 'etherlite/commands/contract/function/encode_arguments'
|
2
|
-
|
3
1
|
module Etherlite::Contract
|
4
2
|
class Function
|
5
|
-
attr_reader :name, :
|
3
|
+
attr_reader :name, :inputs, :outputs
|
6
4
|
|
7
|
-
def initialize(_name,
|
5
|
+
def initialize(_name, _inputs, _outputs, _payable, _constant)
|
8
6
|
@name = _name
|
9
|
-
@
|
7
|
+
@inputs = _inputs
|
8
|
+
@outputs = _outputs
|
10
9
|
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@constant = constant
|
10
|
+
@payable = _payable
|
11
|
+
@constant = _constant
|
14
12
|
end
|
15
13
|
|
16
14
|
def constant?
|
@@ -23,21 +21,27 @@ module Etherlite::Contract
|
|
23
21
|
|
24
22
|
def signature
|
25
23
|
@signature ||= begin
|
26
|
-
arg_signatures = @
|
24
|
+
arg_signatures = @inputs.map &:signature
|
27
25
|
"#{@name}(#{arg_signatures.join(',')})"
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
29
|
def encode(_values)
|
30
|
+
if _values.length != @inputs.count
|
31
|
+
raise ArgumentError, "Expected #{@inputs.count} arguments, got #{_values.length} "
|
32
|
+
end
|
33
|
+
|
32
34
|
signature_hash = Etherlite::Utils.sha3 signature
|
33
|
-
|
35
|
+
encoded_inputs = Etherlite::Support::Array.encode(@inputs, _values)
|
34
36
|
|
35
|
-
'0x' + signature_hash[0..7] +
|
37
|
+
'0x' + signature_hash[0..7] + encoded_inputs
|
36
38
|
end
|
37
39
|
|
38
|
-
def decode(_connection,
|
39
|
-
|
40
|
-
|
40
|
+
def decode(_connection, _data)
|
41
|
+
return nil if @outputs.empty?
|
42
|
+
|
43
|
+
result = Etherlite::Support::Array.decode(_connection, @outputs, _data[2..-1])
|
44
|
+
result.length > 1 ? result : result[0]
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Etherlite
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class RPCError < Error
|
5
|
+
attr_reader :http_status, :http_body
|
6
|
+
|
7
|
+
def initialize(_status, _body)
|
8
|
+
super("Request returned status code #{_status}")
|
9
|
+
@http_status = _status
|
10
|
+
@http_body = _body
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NodeError < Error
|
15
|
+
attr_reader :rpc_error_code, :rpc_error_message
|
16
|
+
|
17
|
+
def initialize(_error)
|
18
|
+
super("#{_error['code']}: #{_error['message']}")
|
19
|
+
@rpc_error_code = _error['code']
|
20
|
+
@rpc_error_message = _error['message']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class NotSupportedError < Error; end
|
25
|
+
|
26
|
+
class UnlinkedContractError < Error; end
|
27
|
+
end
|
@@ -3,30 +3,31 @@ module Etherlite
|
|
3
3
|
@@nonce_cache = {}
|
4
4
|
@@nonce_mutex = Mutex.new
|
5
5
|
|
6
|
+
def self.clear_cache
|
7
|
+
@@nonce_cache = {}
|
8
|
+
end
|
9
|
+
|
6
10
|
def initialize(_connection)
|
7
11
|
@connection = _connection
|
8
12
|
end
|
9
13
|
|
10
|
-
def last_nonce_for(
|
11
|
-
|
12
|
-
last_nonce =
|
13
|
-
last_nonce = last_observed_nonce_for(_address) if last_nonce.nil?
|
14
|
+
def last_nonce_for(_normalized_address)
|
15
|
+
last_nonce = @@nonce_cache[_normalized_address]
|
16
|
+
last_nonce = last_observed_nonce_for(_normalized_address) if last_nonce.nil?
|
14
17
|
last_nonce
|
15
18
|
end
|
16
19
|
|
17
|
-
def with_next_nonce_for(
|
18
|
-
_address = Utils.encode_address_param _address
|
19
|
-
|
20
|
+
def with_next_nonce_for(_normalized_address)
|
20
21
|
@@nonce_mutex.synchronize do
|
21
|
-
next_nonce = last_nonce_for(
|
22
|
+
next_nonce = last_nonce_for(_normalized_address) + 1
|
22
23
|
|
23
24
|
begin
|
24
25
|
result = yield next_nonce
|
25
|
-
@@nonce_cache[
|
26
|
+
@@nonce_cache[_normalized_address] = next_nonce
|
26
27
|
return result
|
27
28
|
rescue
|
28
29
|
# if yield fails, cant be sure about transaction status so must rely again on observing.
|
29
|
-
@@nonce_cache.delete
|
30
|
+
@@nonce_cache.delete _normalized_address
|
30
31
|
raise
|
31
32
|
end
|
32
33
|
end
|
@@ -34,12 +35,10 @@ module Etherlite
|
|
34
35
|
|
35
36
|
private
|
36
37
|
|
37
|
-
def last_observed_nonce_for(
|
38
|
+
def last_observed_nonce_for(_normalized_address)
|
38
39
|
# TODO: support using tx_pool API to improve this:
|
39
40
|
# http://qnimate.com/calculating-nonce-for-raw-transactions-in-geth/
|
40
|
-
|
41
|
-
@connection.ipc_call(:eth_getTransactionCount, _address, 'pending')
|
42
|
-
) - 1
|
41
|
+
@connection.eth_get_transaction_count('0x' + _normalized_address, 'pending') - 1
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|