etheruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|