etherlite 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -92
- data/lib/etherlite.rb +2 -0
- data/lib/etherlite/abi.rb +3 -44
- data/lib/etherlite/commands/abi/load_contract.rb +19 -11
- data/lib/etherlite/commands/abi/load_function.rb +3 -1
- data/lib/etherlite/configuration.rb +2 -1
- data/lib/etherlite/contract/base.rb +17 -38
- data/lib/etherlite/contract/event_base.rb +4 -4
- data/lib/etherlite/contract/function.rb +7 -3
- data/lib/etherlite/contract/function_input.rb +18 -0
- data/lib/etherlite/nonce_manager.rb +7 -4
- data/lib/etherlite/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb97370a946be81259847d2b1f3c7a6da33593e2
|
4
|
+
data.tar.gz: 373696bf36bcf5b5b7889ffe7eadfd0bf3544881
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f45918243e835c01d28c6beb7f3c42ffb77fa35047aab3051aba1f555eb4a65c6e6d7dc0b218a4efbdcb1efef3b3fd3b0b2a1e79f4bd22f202e43085169fc5f7
|
7
|
+
data.tar.gz: 22a6b1e87b0b9778de84607fd2d7d6cc59e35b14c14928ef984d4bfa32901f82d549f7c90ee018071847c69c1ba887e6ec4216989456b5d98efd8c278679d362
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Etherlite
|
2
2
|
|
3
|
-
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/etherlite`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -14,101 +16,13 @@ And then execute:
|
|
14
16
|
|
15
17
|
$ bundle
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
$ rails g etherlite:init
|
19
|
+
Or install it yourself as:
|
20
20
|
|
21
|
-
|
21
|
+
$ gem install etherlite
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
Rails will automatically load the artifacts as ruby classes.
|
28
|
-
|
29
|
-
For example, given the following contract:
|
30
|
-
|
31
|
-
```solidity
|
32
|
-
contract BikeStore {
|
33
|
-
event BikeSold(address buyer, string model);
|
34
|
-
event BikeReturned(address buyer, string reason);
|
35
|
-
|
36
|
-
function buyBike(string _model) payable {
|
37
|
-
// some code
|
38
|
-
}
|
39
|
-
|
40
|
-
function getBikePrice(string _model) returns (uint price) {
|
41
|
-
return _return;
|
42
|
-
}
|
43
|
-
}
|
44
|
-
```
|
45
|
-
|
46
|
-
By copying the contract's artifact file in the `contracts` folder you will be able to call:
|
47
|
-
|
48
|
-
```ruby
|
49
|
-
my_store = BikeStoreContract.at('0xAAAAAAAAAAAAAAAAA') # notice the "Contract" sufix
|
50
|
-
price = my_store.get_bike_price
|
51
|
-
```
|
52
|
-
|
53
|
-
### Accounts
|
54
|
-
|
55
|
-
Etherlite supports both node managed accounts and private key accounts (via the `eth` gem).
|
56
|
-
|
57
|
-
By default etherlite will try to use the first account from the configured node.
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
Etherlite.default_account # this should reference the node's first account (if present)
|
61
|
-
```
|
62
|
-
|
63
|
-
When executing operations that need to send a transaction you will need to provide the account's passphrase. You can set the `passphrase` option in the configuration file:
|
64
|
-
|
65
|
-
```yml
|
66
|
-
development:
|
67
|
-
passphrase: <%= ENV['STORE_PASSPHRASE'] %>
|
68
|
-
```
|
69
|
-
|
70
|
-
or pass it on any transaction-triggering call:
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
my_store.buy_bike('mongoose', passphrase: 'verysecure')
|
74
|
-
```
|
75
|
-
|
76
|
-
If you need to use a private key account, then set the `private_key` option in the configuration file with the account's private key. Etherlite will automatically switch to using a private key account instead of the node account:
|
77
|
-
|
78
|
-
```yml
|
79
|
-
development:
|
80
|
-
private_key: <%= ENV['STORE_PRIVATE_KEY'] %>
|
81
|
-
```
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
Etherlite.default_account.address # this should return the private_key corresponding address
|
85
|
-
```
|
86
|
-
|
87
|
-
If you wish to programatically load a private key account you can call:
|
88
|
-
|
89
|
-
```ruby
|
90
|
-
account = Etherlite.account_from_pk(your_private_key)
|
91
|
-
```
|
92
|
-
|
93
|
-
### Creating contract instances
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
### Calling contract methods
|
98
|
-
|
99
|
-
#### Setting gas limit and price
|
100
|
-
|
101
|
-
### Transferring ether
|
102
|
-
|
103
|
-
|
104
|
-
### Waiting for transactions
|
105
|
-
|
106
|
-
|
107
|
-
### Searching event logs
|
108
|
-
|
109
|
-
### Testing with Etherlite, rspec and testrpc
|
110
|
-
|
111
|
-
Etherlite (will) provides a set of rspec helpers to make
|
25
|
+
TODO: Write usage instructions here
|
112
26
|
|
113
27
|
## Development
|
114
28
|
|
@@ -120,6 +34,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
120
34
|
|
121
35
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/etherlite. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
122
36
|
|
37
|
+
|
123
38
|
## License
|
124
39
|
|
125
40
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/etherlite.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "digest/sha3"
|
2
2
|
require "active_support/all"
|
3
|
+
require "forwardable"
|
3
4
|
require "power-types"
|
4
5
|
|
5
6
|
require "etherlite/version"
|
@@ -25,6 +26,7 @@ require "etherlite/contract/base"
|
|
25
26
|
require "etherlite/contract/event_base"
|
26
27
|
require "etherlite/contract/event_input"
|
27
28
|
require "etherlite/contract/function"
|
29
|
+
require "etherlite/contract/function_input"
|
28
30
|
|
29
31
|
require "etherlite/errors"
|
30
32
|
require "etherlite/configuration"
|
data/lib/etherlite/abi.rb
CHANGED
@@ -3,58 +3,17 @@ require 'etherlite/commands/abi/load_contract'
|
|
3
3
|
require 'etherlite/commands/abi/load_function'
|
4
4
|
|
5
5
|
module Etherlite
|
6
|
-
##
|
7
|
-
# A set of ABI related utilities
|
8
|
-
#
|
9
6
|
module Abi
|
10
7
|
extend self
|
11
8
|
|
12
|
-
##
|
13
|
-
# Load a contract artifact file.
|
14
|
-
#
|
15
|
-
# This method parses a truffle artifact file and loads a **contract** class.
|
16
|
-
#
|
17
|
-
# The generated contract class exposes a series of methods to interact with an instance
|
18
|
-
# of the contract described by the artifact.
|
19
|
-
#
|
20
|
-
# ```
|
21
|
-
# contract_class = Etherlite::Abi.load_contract_at('/path/to/Contract.json')
|
22
|
-
# instance = contract_class.deploy(gas: 1000000)
|
23
|
-
# ```
|
24
|
-
#
|
25
|
-
# @param _path (String) The artifact file absolute path
|
26
|
-
#
|
27
|
-
# @return [Object] the generated contract class.
|
28
|
-
#
|
29
9
|
def load_contract_at(_path)
|
30
|
-
|
31
|
-
LoadContract.for artifact: artifact
|
10
|
+
load_contract JSON.parse File.read(_path)
|
32
11
|
end
|
33
12
|
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
# @param _artifact (Hash) The artifact hash
|
38
|
-
#
|
39
|
-
# @return [Object] the generated contract class.
|
40
|
-
#
|
41
|
-
def load_contract(_artifact)
|
42
|
-
LoadContract.for artifact: _artifact
|
13
|
+
def load_contract(_json)
|
14
|
+
LoadContract.for artifact: _json
|
43
15
|
end
|
44
16
|
|
45
|
-
##
|
46
|
-
# This method is used internally to load a function definition given a function signature.
|
47
|
-
#
|
48
|
-
# The function definition can be later used to call a contract's function without need of
|
49
|
-
# the artifact file.
|
50
|
-
#
|
51
|
-
# @param _signature (String) A string that matches:
|
52
|
-
#
|
53
|
-
# (payable|onchain|<type>) <name>(<type>, <type>, ...), where <name> is the function's name
|
54
|
-
# and <type> is any valid solidity type.
|
55
|
-
#
|
56
|
-
# @return [Object] the generated function definition.
|
57
|
-
#
|
58
17
|
def load_function(_signature)
|
59
18
|
LoadFunction.for signature: _signature
|
60
19
|
end
|
@@ -3,7 +3,7 @@ module Etherlite::Abi
|
|
3
3
|
def perform
|
4
4
|
klass = Class.new(Etherlite::Contract::Base)
|
5
5
|
|
6
|
-
|
6
|
+
define_class_getter klass, 'unlinked_binary', unlinked_binary
|
7
7
|
|
8
8
|
abi_definitions.each do |definition|
|
9
9
|
case definition['type']
|
@@ -11,6 +11,8 @@ module Etherlite::Abi
|
|
11
11
|
define_function klass, definition
|
12
12
|
when 'event'
|
13
13
|
define_event klass, definition
|
14
|
+
when 'constructor'
|
15
|
+
define_class_getter klass, 'constructor', build_function_from_definition(definition)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
@@ -21,6 +23,10 @@ module Etherlite::Abi
|
|
21
23
|
|
22
24
|
private
|
23
25
|
|
26
|
+
def unlinked_binary
|
27
|
+
@artifact['unlinked_binary'] || ''
|
28
|
+
end
|
29
|
+
|
24
30
|
def abi_definitions
|
25
31
|
@artifact['abi'] || []
|
26
32
|
end
|
@@ -52,28 +58,30 @@ module Etherlite::Abi
|
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
55
|
-
event_class
|
56
|
-
event_class
|
61
|
+
define_class_getter event_class, 'original_name', _definition['name']
|
62
|
+
define_class_getter event_class, 'inputs', event_inputs
|
57
63
|
|
58
64
|
_class.events << event_class
|
59
65
|
_class.const_set(_definition['name'], event_class)
|
60
66
|
end
|
61
67
|
|
62
|
-
def
|
63
|
-
unlinked_binary = @artifact['unlinked_binary'] || ''
|
64
|
-
|
68
|
+
def define_class_getter(_class, _name, _value)
|
65
69
|
_class.class_eval do
|
66
|
-
define_singleton_method(
|
70
|
+
define_singleton_method(_name) { _value }
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
70
74
|
def build_function_from_definition(_definition)
|
71
75
|
Etherlite::Contract::Function.new(
|
72
76
|
_definition['name'],
|
73
|
-
_definition['inputs'].map
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+
_definition['inputs'].map do |input|
|
78
|
+
Etherlite::Contract::FunctionInput.new(
|
79
|
+
input['name'], LoadType.for(signature: input['type'])
|
80
|
+
)
|
81
|
+
end,
|
82
|
+
(_definition['outputs'] || []).map { |input| LoadType.for signature: input['type'] },
|
83
|
+
_definition.fetch('payable', false),
|
84
|
+
_definition.fetch('constant', false)
|
77
85
|
)
|
78
86
|
end
|
79
87
|
end
|
@@ -6,7 +6,9 @@ module Etherlite::Abi
|
|
6
6
|
parts = MATCHER.match @signature
|
7
7
|
raise ArgumentError, 'invalid method signature' if parts.nil?
|
8
8
|
|
9
|
-
inputs = parts[3].split(',').map
|
9
|
+
inputs = parts[3].split(',').map do |a|
|
10
|
+
Etherlite::Contract::FunctionInput.new nil, LoadType.for(signature: a.strip)
|
11
|
+
end
|
10
12
|
|
11
13
|
case parts[1]
|
12
14
|
when 'payable'
|
@@ -2,11 +2,12 @@ module Etherlite
|
|
2
2
|
class Configuration
|
3
3
|
DEFAULTS = {
|
4
4
|
url: 'http://127.0.0.1:8545',
|
5
|
+
enable_nonce_cache: false,
|
5
6
|
chain_id: nil, # any chain
|
6
7
|
logger: nil # set by method
|
7
8
|
}
|
8
9
|
|
9
|
-
attr_accessor :url, :chain_id, :logger
|
10
|
+
attr_accessor :url, :chain_id, :logger, :enable_nonce_cache
|
10
11
|
|
11
12
|
def initialize
|
12
13
|
assign_attributes DEFAULTS
|
@@ -1,21 +1,23 @@
|
|
1
1
|
module Etherlite::Contract
|
2
|
-
##
|
3
|
-
# The base class for etherlite contract classes
|
4
|
-
#
|
5
2
|
class Base
|
6
3
|
include Etherlite::Api::Address
|
7
4
|
|
8
|
-
# The contract's registered functions
|
9
5
|
def self.functions
|
10
6
|
@functions ||= []
|
11
7
|
end
|
12
8
|
|
13
|
-
# The contract's registered events
|
14
9
|
def self.events
|
15
10
|
@events ||= []
|
16
11
|
end
|
17
12
|
|
18
|
-
|
13
|
+
def self.unlinked_binary
|
14
|
+
'0x0'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.constructor
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
19
21
|
def self.binary
|
20
22
|
@binary ||= begin
|
21
23
|
if /__[^_]+_+/ === unlinked_binary
|
@@ -26,37 +28,19 @@ module Etherlite::Contract
|
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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))
|
31
|
+
def self.deploy(*_args)
|
32
|
+
options = _args.last.is_a?(Hash) ? _args.pop : {}
|
33
|
+
as = options[:as] || options[:client].try(:default_account) || Etherlite.default_account
|
34
|
+
|
35
|
+
tx_data = binary
|
36
|
+
tx_data += constructor.encode(_args) unless constructor.nil?
|
37
|
+
|
38
|
+
tx = as.send_transaction({ data: tx_data }.merge(options))
|
39
|
+
if tx.wait_for_block(timeout: options.fetch(:timeout, 120))
|
51
40
|
at tx.contract_address, as: as
|
52
41
|
end
|
53
42
|
end
|
54
43
|
|
55
|
-
##
|
56
|
-
# Creates a new instance of the contract class that points to a given address.
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
44
|
def self.at(_address, client: nil, as: nil)
|
61
45
|
_address = Etherlite::Utils.normalize_address_param _address
|
62
46
|
|
@@ -76,11 +60,6 @@ module Etherlite::Contract
|
|
76
60
|
@default_account = _default_account
|
77
61
|
end
|
78
62
|
|
79
|
-
##
|
80
|
-
# Searches for event logs emitted by this contract
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
63
|
def get_logs(events: nil, from_block: :earliest, to_block: :latest)
|
85
64
|
params = {
|
86
65
|
address: json_encoded_address,
|
@@ -3,17 +3,17 @@ require 'etherlite/commands/contract/event_base/decode_log_inputs'
|
|
3
3
|
module Etherlite::Contract
|
4
4
|
class EventBase
|
5
5
|
def self.inputs
|
6
|
-
|
6
|
+
nil # To be implemented by sub classes
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.original_name
|
10
|
-
|
10
|
+
nil # To be implemented by sub classes
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.signature
|
14
14
|
@signature ||= begin
|
15
|
-
input_sig =
|
16
|
-
"#{
|
15
|
+
input_sig = inputs.map { |i| i.type.signature }
|
16
|
+
"#{original_name}(#{input_sig.join(',')})"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -31,10 +31,14 @@ module Etherlite::Contract
|
|
31
31
|
raise ArgumentError, "Expected #{@inputs.count} arguments, got #{_values.length} "
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
encoded_inputs = Etherlite::Support::Array.encode(@inputs, _values)
|
34
|
+
encoded_inputs = Etherlite::Support::Array.encode(@inputs.map(&:type), _values)
|
36
35
|
|
37
|
-
|
36
|
+
if @name
|
37
|
+
signature_hash = Etherlite::Utils.sha3 signature
|
38
|
+
'0x' + signature_hash[0..7] + encoded_inputs
|
39
|
+
else
|
40
|
+
encoded_inputs # if no name is provided, just render arguments
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
def decode(_connection, _data)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Etherlite::Contract
|
2
|
+
class FunctionInput
|
3
|
+
attr_reader :original_name, :type
|
4
|
+
|
5
|
+
def initialize(_original_name, _type)
|
6
|
+
@original_name = _original_name
|
7
|
+
@type = _type
|
8
|
+
end
|
9
|
+
|
10
|
+
def signature
|
11
|
+
@type.signature
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name ||= @original_name.underscore
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -23,11 +23,11 @@ module Etherlite
|
|
23
23
|
|
24
24
|
begin
|
25
25
|
result = yield next_nonce
|
26
|
-
@@nonce_cache[_normalized_address] = next_nonce
|
26
|
+
@@nonce_cache[_normalized_address] = next_nonce if caching_enabled?
|
27
27
|
return result
|
28
28
|
rescue
|
29
29
|
# if yield fails, cant be sure about transaction status so must rely again on observing.
|
30
|
-
@@nonce_cache.delete _normalized_address
|
30
|
+
@@nonce_cache.delete _normalized_address if caching_enabled?
|
31
31
|
raise
|
32
32
|
end
|
33
33
|
end
|
@@ -36,9 +36,12 @@ module Etherlite
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def last_observed_nonce_for(_normalized_address)
|
39
|
-
#
|
40
|
-
# http://qnimate.com/calculating-nonce-for-raw-transactions-in-geth/
|
39
|
+
# https://github.com/ethereum/go-ethereum/issues/2736
|
41
40
|
@connection.eth_get_transaction_count('0x' + _normalized_address, 'pending') - 1
|
42
41
|
end
|
42
|
+
|
43
|
+
def caching_enabled?
|
44
|
+
Etherlite.config.enable_nonce_cache
|
45
|
+
end
|
43
46
|
end
|
44
47
|
end
|
data/lib/etherlite/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etherlite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ignacio Baixas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest-sha3
|
@@ -205,6 +205,7 @@ files:
|
|
205
205
|
- lib/etherlite/contract/event_base.rb
|
206
206
|
- lib/etherlite/contract/event_input.rb
|
207
207
|
- lib/etherlite/contract/function.rb
|
208
|
+
- lib/etherlite/contract/function_input.rb
|
208
209
|
- lib/etherlite/errors.rb
|
209
210
|
- lib/etherlite/nonce_manager.rb
|
210
211
|
- lib/etherlite/railtie.rb
|