etherlite 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c11198a345cd94f0ef94e90a80187b8bd06b382
|
4
|
+
data.tar.gz: 871fddbd0b57e525285964b3be91327ab9b6b08d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7994ac39cbb16387397ec0829891bc401e9d425bc62051c46b7a47244759b0d018626af088df38456060ba8558ed29836ae666be4f932fc301d093d8233bf33a
|
7
|
+
data.tar.gz: db831cc13de2c7f578e0659951ddde2e91697369c63af597e091b2eec1a44eb881ca882ec754184f9facf2128d035eb319a6537c0c9c05fec23a4c42bb8bbf87
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Etherlite
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Etherlite is a
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -16,13 +14,101 @@ And then execute:
|
|
16
14
|
|
17
15
|
$ bundle
|
18
16
|
|
19
|
-
|
17
|
+
Initialize the configuration file and `contracts` folder:
|
18
|
+
|
19
|
+
$ rails g etherlite:init
|
20
20
|
|
21
|
-
|
21
|
+
A `etherlite.yml` file will be added to your app's `config` folder, there you can configure the node locations and some other variables for each enviroment.
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
This gem is intended to be used with contract artifacts generated by Truffle. To start interacting with your contracts, drop the artifacts for the contracts you will be using in the application `contracts` folder. (You can find the contract artifacts on the `build/contracts` folder of your truffle proyect).
|
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
|
26
112
|
|
27
113
|
## Development
|
28
114
|
|
@@ -34,7 +120,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
34
120
|
|
35
121
|
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.
|
36
122
|
|
37
|
-
|
38
123
|
## License
|
39
124
|
|
40
125
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/etherlite.gemspec
CHANGED
@@ -29,4 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
30
|
spec.add_development_dependency "guard", "~> 2.14"
|
31
31
|
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
32
|
+
spec.add_development_dependency "webmock", "~> 3.0.1"
|
33
|
+
spec.add_development_dependency "pry"
|
32
34
|
end
|
data/lib/etherlite.rb
CHANGED
@@ -6,13 +6,15 @@ require "etherlite/version"
|
|
6
6
|
|
7
7
|
require "etherlite/api/address"
|
8
8
|
require "etherlite/api/node"
|
9
|
+
require "etherlite/api/rpc"
|
10
|
+
|
11
|
+
require "etherlite/support/array"
|
9
12
|
|
10
13
|
require "etherlite/types/base"
|
11
14
|
require "etherlite/types/address"
|
12
|
-
require "etherlite/types/array_base"
|
13
15
|
require "etherlite/types/array_dynamic"
|
14
16
|
require "etherlite/types/array_fixed"
|
15
|
-
require "etherlite/types/
|
17
|
+
require "etherlite/types/boolean"
|
16
18
|
require "etherlite/types/byte_string"
|
17
19
|
require "etherlite/types/bytes"
|
18
20
|
require "etherlite/types/fixed"
|
@@ -24,14 +26,17 @@ require "etherlite/contract/event_base"
|
|
24
26
|
require "etherlite/contract/event_input"
|
25
27
|
require "etherlite/contract/function"
|
26
28
|
|
29
|
+
require "etherlite/errors"
|
27
30
|
require "etherlite/configuration"
|
28
31
|
require "etherlite/abi"
|
29
32
|
require "etherlite/utils"
|
30
33
|
require "etherlite/connection"
|
31
34
|
require "etherlite/transaction"
|
32
35
|
require "etherlite/nonce_manager"
|
33
|
-
require "etherlite/
|
34
|
-
require "etherlite/account"
|
36
|
+
require "etherlite/account/base"
|
37
|
+
require "etherlite/account/local"
|
38
|
+
require "etherlite/account/private_key"
|
39
|
+
require "etherlite/account/anonymous"
|
35
40
|
require "etherlite/address"
|
36
41
|
require "etherlite/client"
|
37
42
|
|
@@ -42,10 +47,10 @@ module Etherlite
|
|
42
47
|
Utils.valid_address? _value
|
43
48
|
end
|
44
49
|
|
45
|
-
def self.connect(_url)
|
50
|
+
def self.connect(_url, _options = {})
|
46
51
|
_url = URI(_url) unless _url.is_a? URI
|
47
52
|
|
48
|
-
Client.new Connection.new(_url)
|
53
|
+
Client.new Connection.new(_url, _options.fetch(:chain_id, config.chain_id))
|
49
54
|
end
|
50
55
|
|
51
56
|
def self.config
|
@@ -62,7 +67,7 @@ module Etherlite
|
|
62
67
|
end
|
63
68
|
|
64
69
|
def self.connection
|
65
|
-
@connection ||= Connection.new
|
70
|
+
@connection ||= Connection.new(URI(config.url), config.chain_id)
|
66
71
|
end
|
67
72
|
end
|
68
73
|
|
data/lib/etherlite/abi.rb
CHANGED
@@ -3,18 +3,58 @@ 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
|
+
#
|
6
9
|
module Abi
|
7
10
|
extend self
|
8
11
|
|
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
|
+
#
|
9
29
|
def load_contract_at(_path)
|
10
|
-
|
11
|
-
LoadContract.for
|
30
|
+
artifact = JSON.parse File.read(_path)
|
31
|
+
LoadContract.for artifact: artifact
|
12
32
|
end
|
13
33
|
|
14
|
-
|
15
|
-
|
34
|
+
##
|
35
|
+
# This method is similar to `load_contract_at` but takes a hash instead of the raw file.
|
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
|
16
43
|
end
|
17
44
|
|
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
|
+
#
|
18
58
|
def load_function(_signature)
|
19
59
|
LoadFunction.for signature: _signature
|
20
60
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Etherlite::Account
|
2
|
+
class Base
|
3
|
+
include Etherlite::Api::Address
|
4
|
+
|
5
|
+
attr_reader :connection, :normalized_address
|
6
|
+
|
7
|
+
def initialize(_connection, _normalized_address = nil)
|
8
|
+
@connection = _connection
|
9
|
+
@normalized_address = _normalized_address
|
10
|
+
end
|
11
|
+
|
12
|
+
def transfer_to(_target, _options = {})
|
13
|
+
send_transaction _options.merge(to: _target, value: _options.fetch(:amount, 0))
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(_target, _function, *_params)
|
17
|
+
_function = parse_function(_function) if _function.is_a? String
|
18
|
+
options = _params.last.is_a?(Hash) ? _params.pop.clone : {}
|
19
|
+
|
20
|
+
if _function.constant?
|
21
|
+
call_constant _target, _function, _params, options
|
22
|
+
else
|
23
|
+
value = options.fetch(:pay, 0)
|
24
|
+
raise 'function is not payable' if value > 0 && !_function.payable?
|
25
|
+
|
26
|
+
send_transaction options.merge(
|
27
|
+
to: _target, data: _function.encode(_params), value: value
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_transaction(_options = {})
|
33
|
+
raise NotSupportedError, 'transactions are not supported by this kind of account'
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def call_constant(_target, _function, _params, _options)
|
39
|
+
params = {
|
40
|
+
from: json_encoded_address,
|
41
|
+
to: Etherlite::Utils.encode_address_param(_target),
|
42
|
+
data: _function.encode(_params)
|
43
|
+
}
|
44
|
+
|
45
|
+
params[:from] = json_encoded_address if @normalized_address.present?
|
46
|
+
|
47
|
+
block = Etherlite::Utils.encode_block_param _options.fetch(:block, :latest)
|
48
|
+
|
49
|
+
_function.decode @connection, @connection.eth_call(params, block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_function(_signature)
|
53
|
+
Abi::LoadFunction.for signature: _signature
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Etherlite
|
2
|
+
module Account
|
3
|
+
class Local < Base
|
4
|
+
def unlock(_passphrase)
|
5
|
+
@passphrase = _passphrase
|
6
|
+
end
|
7
|
+
|
8
|
+
def lock
|
9
|
+
@passphrase = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_transaction(_options = {})
|
13
|
+
params = build_params_from_options _options
|
14
|
+
passphrase = _options.fetch(:passphrase, @passphrase)
|
15
|
+
|
16
|
+
Transaction.new @connection, send_transaction_with_passphrase(params, passphrase)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def build_params_from_options(_opt)
|
22
|
+
{ from: json_encoded_address }.tap do |pr|
|
23
|
+
pr[:to] = Utils.encode_address_param(_opt[:to]) if _opt.key? :to
|
24
|
+
pr[:value] = Utils.encode_quantity_param(_opt[:value]) if _opt.key? :value
|
25
|
+
pr[:data] = _opt[:data] if _opt.key? :data
|
26
|
+
pr[:gas] = Utils.encode_quantity_param(_opt[:gas]) if _opt.key? :gas
|
27
|
+
pr[:gasPrice] = Utils.encode_quantity_param(_opt[:gas_price]) if _opt.key? :gas_price
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_transaction_with_passphrase(_params, _passphrase)
|
32
|
+
if _passphrase.nil?
|
33
|
+
@connection.eth_send_transaction _params
|
34
|
+
else
|
35
|
+
@connection.personal_send_transaction _params, _passphrase
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'eth'
|
2
|
+
|
3
|
+
module Etherlite
|
4
|
+
module Account
|
5
|
+
class PrivateKey < Base
|
6
|
+
def initialize(_connection, _pk)
|
7
|
+
@key = Eth::Key.new priv: _pk
|
8
|
+
super _connection, Etherlite::Utils.normalize_address(@key.address)
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_transaction(_options = {})
|
12
|
+
nonce_manager.with_next_nonce_for(normalized_address) do |nonce|
|
13
|
+
tx = Eth::Tx.new(
|
14
|
+
value: _options.fetch(:value, 0),
|
15
|
+
data: _options.fetch(:data, ''),
|
16
|
+
gas_limit: _options.fetch(:gas, 90_000),
|
17
|
+
gas_price: _options.fetch(:gas_price, gas_price),
|
18
|
+
to: Etherlite::Utils.encode_address_param(_options[:to]),
|
19
|
+
nonce: nonce
|
20
|
+
)
|
21
|
+
|
22
|
+
# Since eth gem does not allow configuration of chains for every tx, we need
|
23
|
+
# to globally configure it before signing. This is not thread safe so a mutex is needed.
|
24
|
+
sign_with_connection_chain tx
|
25
|
+
|
26
|
+
Etherlite::Transaction.new @connection, @connection.eth_send_raw_transaction(tx.hex)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
@@eth_mutex = Mutex.new
|
33
|
+
|
34
|
+
def gas_price
|
35
|
+
# TODO: improve on this
|
36
|
+
@gas_price ||= connection.eth_gas_price
|
37
|
+
end
|
38
|
+
|
39
|
+
def nonce_manager
|
40
|
+
Etherlite::NonceManager.new @connection
|
41
|
+
end
|
42
|
+
|
43
|
+
def sign_with_connection_chain(_tx)
|
44
|
+
@@eth_mutex.synchronize do
|
45
|
+
Eth.configure { |c| c.chain_id = @connection.chain_id }
|
46
|
+
_tx.sign @key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/etherlite/api/node.rb
CHANGED
@@ -5,37 +5,40 @@ module Etherlite
|
|
5
5
|
include Address
|
6
6
|
|
7
7
|
def get_block_number
|
8
|
-
|
8
|
+
connection.eth_block_number
|
9
9
|
end
|
10
10
|
|
11
11
|
def get_gas_price
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def get_transaction_receipt(_tx_hash)
|
16
|
-
connection.ipc_call(:eth_getTransactionReceipt, _tx_hash)
|
12
|
+
connection.eth_gas_price
|
17
13
|
end
|
18
14
|
|
19
15
|
def register_account(_passphrase)
|
20
16
|
address = connection.ipc_call(:personal_newAccount, _passphrase)
|
21
|
-
Etherlite::Account.new @connection, Etherlite::Utils.normalize_address(address)
|
17
|
+
Etherlite::Account::Local.new @connection, Etherlite::Utils.normalize_address(address)
|
22
18
|
end
|
23
19
|
|
24
20
|
def accounts
|
25
21
|
connection.ipc_call(:eth_accounts).map do |address|
|
26
|
-
Etherlite::Account.new @connection, Etherlite::Utils.normalize_address(address)
|
22
|
+
Etherlite::Account::Local.new @connection, Etherlite::Utils.normalize_address(address)
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
30
|
-
def
|
31
|
-
@
|
26
|
+
def default_account
|
27
|
+
@default_account ||= load_default_account
|
32
28
|
end
|
33
29
|
|
34
30
|
def account_from_pk(_pk)
|
35
|
-
Etherlite::
|
31
|
+
Etherlite::Account::PrivateKey.new connection, _pk
|
36
32
|
end
|
37
33
|
|
38
|
-
def_delegators :
|
34
|
+
def_delegators :default_account, :unlock, :lock, :normalized_address, :transfer_to, :call
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def load_default_account
|
39
|
+
# TODO: consider configuring a global PK and allow the default account to use it
|
40
|
+
accounts.first || Etherlite::Account::Anonymous.new(connection)
|
41
|
+
end
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|