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 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: []