etheruby 0.0.1
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/lib/etheruby.rb +67 -0
- data/lib/etheruby/arguments_generator.rb +118 -0
- data/lib/etheruby/client.rb +36 -0
- data/lib/etheruby/contract_method_dsl.rb +42 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 12c070c1b68b2effbf8d546fe905fd3d184bc26f
|
4
|
+
data.tar.gz: de4aa5107ea7cd347aabed1848eec9fb9f612f0d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c914deee7b439f63d11111e3fe16be892e18da1d115722faab9505ee965d1905ce0d361f6599e8920521237bea1578525e0772278b0cfdb19ef424511c12457
|
7
|
+
data.tar.gz: 2424d20153c2fa7c0a818c347e9535b197843c8d01d9a36d9f32cb2d285cb3685ed82b653dec7f244e2479806d2a006be2ee3c17ea5cdb93d99cb5f5aa603cc8
|
data/lib/etheruby.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require_relative 'etheruby/client'
|
4
|
+
require_relative 'etheruby/contract_method_dsl'
|
5
|
+
require_relative 'etheruby/arguments_generator'
|
6
|
+
|
7
|
+
module Etheruby
|
8
|
+
class NoContractMethodError < StandardError; end
|
9
|
+
|
10
|
+
def contract
|
11
|
+
Class.new do
|
12
|
+
def self.inherited(subclass)
|
13
|
+
@@c_methods = {}
|
14
|
+
@@logger = ::Logger.new(STDOUT)
|
15
|
+
@@logger.level = if ENV.has_key? 'ETHERUBY_DEBUG'
|
16
|
+
::Logger::DEBUG
|
17
|
+
else
|
18
|
+
::Logger::WARN
|
19
|
+
end
|
20
|
+
@@logger.progname = "Etheruby Contract '#{subclass.name}'"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.at_address(address)
|
24
|
+
@@address = address
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.method(name, &blk)
|
28
|
+
cmd = ContractMethodDSL.new(name)
|
29
|
+
cmd.instance_exec &blk if blk
|
30
|
+
@@c_methods[name] = cmd.validate!
|
31
|
+
@@logger.debug("Registred method #{name}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.method_missing(sym, *args)
|
35
|
+
raise NoContractMethodError.new (
|
36
|
+
"The method #{sym} does not exist in the #{self.class.to_s} contract."
|
37
|
+
) unless @@c_methods.include? sym
|
38
|
+
execute_contract_method(@@c_methods[sym], args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.execute_contract_method(method_info, args)
|
42
|
+
arguments = ArgumentsGenerator.new(method_info[:params], args).to_s
|
43
|
+
data = "#{method_info[:signature]}#{arguments}"
|
44
|
+
composed_body = { to: self.address, data: data }
|
45
|
+
[ :gas, :gasPrice, :value, :from ].each { |kw|
|
46
|
+
composed_body[kw] = method_info[kw] if method_info.has_key? kw
|
47
|
+
}
|
48
|
+
@@logger.debug("Calling #{method_info[:name]} with parameters #{composed_body.inspect}")
|
49
|
+
response = Client.eth.call composed_body, "latest"
|
50
|
+
if response.has_key? 'error'
|
51
|
+
@@logger.error("Failed contract execution #{response['error']['message']}")
|
52
|
+
else
|
53
|
+
@@logger.debug("Response from API for #{method_info[:name]} : #{response.inspect}")
|
54
|
+
end
|
55
|
+
response['result']
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.address
|
59
|
+
"0x#{@@address.to_s(16)}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module_function :contract
|
65
|
+
|
66
|
+
Contract = Etheruby::contract
|
67
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Etheruby
|
2
|
+
|
3
|
+
class IncorrectTypeError < StandardError; end
|
4
|
+
class ArgumentsCountError < StandardError; end
|
5
|
+
|
6
|
+
# https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
7
|
+
class ArgumentsGenerator
|
8
|
+
|
9
|
+
attr_reader :params, :args
|
10
|
+
|
11
|
+
def initialize(params, args)
|
12
|
+
@params = params
|
13
|
+
@args = args
|
14
|
+
end
|
15
|
+
|
16
|
+
def is_sized_type(param)
|
17
|
+
param.to_s.match /^([a-z]+)(\d+)$/
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_static_array_type(param)
|
21
|
+
param.to_s.match(/^([a-z]+)\[(\d+)\]$/)
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_dynamic_array_type(param)
|
25
|
+
param.to_s.match(/^([a-z]+)\[\]$/)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
raise ArgumentsCountError.new unless params.count == args.count
|
30
|
+
arguments = ""
|
31
|
+
# For each parameter of the method called, we
|
32
|
+
# match the corresponding type to encode and we send the
|
33
|
+
# parameter given to this encoder
|
34
|
+
(0..params.count-1).each do |i|
|
35
|
+
param, arg = params[i], args[i]
|
36
|
+
if match = is_sized_type(param)
|
37
|
+
# Parameter is a sized type, e.g. uint256, byte32 ...
|
38
|
+
method_name = "#{match[1]}_encode".to_sym
|
39
|
+
if respond_to?(method_name)
|
40
|
+
arguments += send(method_name, match[2].to_i, arg)
|
41
|
+
else
|
42
|
+
raise IncorrectTypeError.new("Type #{param} cannot be encoded")
|
43
|
+
end
|
44
|
+
elsif match = is_static_array_type(param)
|
45
|
+
# Parameter is a staticly sized array type, e.g. uint256[24]
|
46
|
+
arguments += static_array(match[1], match[2].to_i, arg)
|
47
|
+
elsif match = is_dynamic_array_type(param)
|
48
|
+
# Parameter is a dynamicaly sized array type, e.g. uint256[]
|
49
|
+
arguments += dynamic_array(match[1], arg)
|
50
|
+
else
|
51
|
+
# Parameter is a single-word type : string, bytes, address etc...
|
52
|
+
method_name = "#{param}_encode".to_sym
|
53
|
+
if respond_to?(method_name)
|
54
|
+
arguments += send(method_name, arg)
|
55
|
+
else
|
56
|
+
raise IncorrectTypeError.new("Type #{param} cannot be encoded")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
arguments
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# int<X> encoding
|
65
|
+
def int_encode(size, arg)
|
66
|
+
if arg >= 0
|
67
|
+
arg.to_s(16).rjust(size / 4, '0')
|
68
|
+
else
|
69
|
+
mask = (1 << size) - 1
|
70
|
+
(arg & mask).to_s(16)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# uint<X> encoding
|
76
|
+
def uint_encode(size, arg)
|
77
|
+
arg.to_s(16).rjust(size / 4,'0')
|
78
|
+
end
|
79
|
+
|
80
|
+
def fixed_encode(size, arg)
|
81
|
+
#Todo
|
82
|
+
end
|
83
|
+
|
84
|
+
def ufixed_encode(size, arg)
|
85
|
+
#Todo
|
86
|
+
end
|
87
|
+
|
88
|
+
def byte_encode(size, arg)
|
89
|
+
arg.map{ |b| b.to_s(16) }.join.rjust(size,'0')
|
90
|
+
end
|
91
|
+
|
92
|
+
def static_array(type, size, arg)
|
93
|
+
#Todo
|
94
|
+
end
|
95
|
+
|
96
|
+
def dynamic_array(type, arg)
|
97
|
+
#Todo
|
98
|
+
end
|
99
|
+
|
100
|
+
def address_encode(arg)
|
101
|
+
uint_encode(160, arg)
|
102
|
+
end
|
103
|
+
|
104
|
+
def string_encode(arg)
|
105
|
+
#Todo
|
106
|
+
end
|
107
|
+
|
108
|
+
def bytes_encode(arg)
|
109
|
+
#Todo
|
110
|
+
end
|
111
|
+
|
112
|
+
def bool_encode(arg)
|
113
|
+
#Todo
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module Etheruby
|
6
|
+
|
7
|
+
class Client
|
8
|
+
def self.uri=(_uri)
|
9
|
+
@@uri = URI.parse(_uri)
|
10
|
+
end
|
11
|
+
def self.method_missing(sym)
|
12
|
+
@@uri ||= URI.parse('http://localhost:8545/')
|
13
|
+
ClientHolder.new(@@uri,sym)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ClientHolder
|
18
|
+
def initialize(uri,sym)
|
19
|
+
@uri = uri
|
20
|
+
@sym = sym
|
21
|
+
end
|
22
|
+
def method_missing(sym, *data)
|
23
|
+
body = { 'id' => 'jsonrpc', 'method' => "#{@sym}_#{sym}", 'params' => data }
|
24
|
+
text_response = http_post_request(::MultiJson.dump(body))
|
25
|
+
::MultiJson.load(text_response)
|
26
|
+
end
|
27
|
+
def http_post_request(post_body)
|
28
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
29
|
+
request = Net::HTTP::Post.new(@uri.request_uri)
|
30
|
+
request.content_type = 'application/json'
|
31
|
+
request.body = post_body
|
32
|
+
http.request(request).body
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'digest/sha3'
|
2
|
+
|
3
|
+
module Etheruby
|
4
|
+
|
5
|
+
class ContractMethodDSL
|
6
|
+
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
def initialize(name)
|
10
|
+
@data = { name: name }
|
11
|
+
end
|
12
|
+
|
13
|
+
def parameters(*args)
|
14
|
+
data[:params] = args
|
15
|
+
end
|
16
|
+
|
17
|
+
def array(type, size=nil)
|
18
|
+
if size
|
19
|
+
"#{type}[#{size}]"
|
20
|
+
else
|
21
|
+
"#{type}[]"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def returns(*args)
|
26
|
+
data[:returns] = args
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(sym, *args)
|
30
|
+
data[sym] = args[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate!
|
34
|
+
data[:params] = [] unless data.has_key? :params
|
35
|
+
signature = "#{@data[:name]}(#{data[:params].join(',')})"
|
36
|
+
data[:signature] = "0x#{Digest::SHA3.hexdigest(signature,256)[0..7]}"
|
37
|
+
data
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: etheruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jérémy SEBAN
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: digest-sha3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: simplecov
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: multi_json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.12'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.5'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.5'
|
83
|
+
description: Describe ethereum smart-contract and execute them with ease.
|
84
|
+
email: jeremy@seban.eu
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- lib/etheruby.rb
|
90
|
+
- lib/etheruby/arguments_generator.rb
|
91
|
+
- lib/etheruby/client.rb
|
92
|
+
- lib/etheruby/contract_method_dsl.rb
|
93
|
+
homepage: https://github.com/MechanicalSloth/etheruby
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '2.3'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.5.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: 'Etheruby : Ethereum smart-contract classy-describer for Ruby.'
|
117
|
+
test_files: []
|