web3ethereum 1.0.0

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.
@@ -0,0 +1,171 @@
1
+ module Web3
2
+ module Eth
3
+ class Contract
4
+
5
+ class ContractInstance
6
+
7
+ def initialize contract, address
8
+ @contract = contract
9
+ @address = address
10
+ end
11
+
12
+ def method_missing m, *args
13
+ @contract.call_contract @address, m.to_s, args
14
+ end
15
+
16
+ def __contract__
17
+ @contract
18
+ end
19
+
20
+ def __address__
21
+ @address
22
+ end
23
+
24
+ end
25
+
26
+ class ContractMethod
27
+
28
+ include Abi::AbiCoder
29
+ include Abi::Utils
30
+ include Utility
31
+
32
+ attr_reader :abi, :signature, :name, :signature_hash, :input_types, :output_types, :constant
33
+
34
+ def initialize abi
35
+ @abi = abi
36
+ @name = abi['name']
37
+ @constant = !!abi['constant']
38
+ @input_types = abi['inputs'].map{|a| a['type']}
39
+ @output_types = abi['outputs'].map{|a| a['type']} if abi['outputs']
40
+ @signature = Abi::Utils.function_signature @name, @input_types
41
+ @signature_hash = Abi::Utils.signature_hash @signature, (abi['type']=='event' ? 64 : 8)
42
+ end
43
+
44
+ def parse_event_args log
45
+
46
+ log_data = remove_0x_head log.raw_data['data']
47
+ indexed_types = abi['inputs'].select{|a| a['indexed']}.collect{|a| a['type']}
48
+ not_indexed_types = abi['inputs'].select{|a| !a['indexed']}.collect{|a| a['type']}
49
+
50
+ indexed_args = log.indexed_args
51
+
52
+ if indexed_args.size==indexed_types.size
53
+
54
+ indexed_values = [indexed_types, indexed_args].transpose.collect{|arg|
55
+ decode_typed_data( arg.first, [arg.second].pack('H*') )
56
+ }
57
+
58
+ not_indexed_values = not_indexed_types.empty? ? [] :
59
+ decode_abi(not_indexed_types, [log_data].pack('H*') )
60
+
61
+ i = j = 0
62
+
63
+ abi['inputs'].collect{|input|
64
+ input['indexed'] ? (i+=1; indexed_values[i-1]) : (j+=1;not_indexed_values[j-1])
65
+ }
66
+
67
+ elsif !indexed_args.empty? || !log_data.empty?
68
+ all_types = abi['inputs'].collect{|a| a['type']}
69
+ [all_types[0...indexed_args.size], indexed_args].transpose.collect{|arg|
70
+ decode_typed_data( arg.first, [arg.second].pack('H*') )
71
+ } + decode_abi(all_types[indexed_args.size..-1], [log_data].pack('H*') )
72
+ else
73
+ []
74
+ end
75
+
76
+ end
77
+
78
+
79
+ def parse_method_args transaction
80
+ d = transaction.call_input_data
81
+ (!d || d.empty?) ? [] : decode_abi(input_types, [d].pack('H*'))
82
+ end
83
+
84
+ def do_call web3_rpc, contract_address, args
85
+ data = '0x' + signature_hash + encode_hex(encode_abi(input_types, args) )
86
+
87
+ response = web3_rpc.request "eth_call", [{ to: contract_address, data: data}, 'latest']
88
+
89
+ string_data = [remove_0x_head(response)].pack('H*')
90
+ return nil if string_data.empty?
91
+
92
+ result = decode_abi output_types, string_data
93
+ result.length==1 ? result.first : result
94
+ end
95
+
96
+ end
97
+
98
+ attr_reader :web3_rpc, :abi, :functions, :events, :constructor, :functions_by_hash, :events_by_hash
99
+
100
+ def initialize abi, web_rpc = nil
101
+ @web3_rpc = web_rpc
102
+ @abi = abi.kind_of?(String) ? JSON.parse(abi) : abi
103
+ parse_abi @abi
104
+ end
105
+
106
+ def at address
107
+ ContractInstance.new self, address
108
+ end
109
+
110
+ def call_contract contract_address, method_name, args
111
+ function = functions[method_name]
112
+ raise "No method found in ABI: #{method_name}" unless function
113
+ raise "Function #{method_name} is not constant: #{method_name}, requires to sign transaction" unless function.constant
114
+ function.do_call web3_rpc, contract_address, args
115
+ end
116
+
117
+ def find_event_by_hash method_hash
118
+ @events_by_hash[method_hash]
119
+ end
120
+
121
+ def find_function_by_hash method_hash
122
+ @functions_by_hash[method_hash]
123
+ end
124
+
125
+ def parse_log_args log
126
+ event = find_event_by_hash log.method_hash
127
+ raise "No event found by hash #{log.method_hash}, probably ABI is not related to log event" unless event
128
+ event.parse_event_args log
129
+ end
130
+
131
+ def parse_call_args transaction
132
+ function = find_function_by_hash transaction.method_hash
133
+ raise "No function found by hash #{transaction.method_hash}, probably ABI is not related to call" unless function
134
+ function.parse_method_args transaction
135
+ end
136
+
137
+
138
+ def parse_constructor_args transaction
139
+ constructor ? constructor.parse_method_args(transaction) : []
140
+ end
141
+
142
+ private
143
+
144
+ def parse_abi abi
145
+ @functions = {}
146
+ @events = {}
147
+
148
+ @functions_by_hash = {}
149
+ @events_by_hash = {}
150
+
151
+ abi.each{|a|
152
+
153
+ case a['type']
154
+ when 'function'
155
+ method = ContractMethod.new(a)
156
+ @functions[method.name] = method
157
+ @functions_by_hash[method.signature_hash] = method
158
+ when 'event'
159
+ method = ContractMethod.new(a)
160
+ @events[method.name] = method
161
+ @events_by_hash[method.signature_hash] = method
162
+ when 'constructor'
163
+ method = ContractMethod.new(a)
164
+ @constructor = method
165
+ end
166
+ }
167
+ end
168
+
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,52 @@
1
+ module Web3
2
+ module Eth
3
+
4
+ class EthModule
5
+
6
+ include Web3::Eth::Utility
7
+
8
+ PREFIX = 'eth_'
9
+
10
+ def initialize web3_rpc
11
+ @web3_rpc = web3_rpc
12
+ end
13
+
14
+ def getBalance address, block = 'latest', convert_to_eth = true
15
+ wei = @web3_rpc.request("#{PREFIX}#{__method__}", [address, block]).to_i 16
16
+ convert_to_eth ? wei_to_ether(wei) : wei
17
+ end
18
+
19
+ def getBlockByNumber block, full = true, convert_to_object = true
20
+ resp = @web3_rpc.request("#{PREFIX}#{__method__}", [hex(block), full])
21
+ convert_to_object ? Block.new(resp) : resp
22
+ end
23
+
24
+ def blockNumber
25
+ from_hex @web3_rpc.request("#{PREFIX}#{__method__}")
26
+ end
27
+
28
+ def getTransactionByHash tx_hash
29
+ Transaction.new @web3_rpc.request("#{PREFIX}#{__method__}", [tx_hash])
30
+ end
31
+
32
+ def getTransactionReceipt tx_hash
33
+ TransactionReceipt.new @web3_rpc.request("#{PREFIX}#{__method__}", [tx_hash])
34
+ end
35
+
36
+ def contract abi
37
+ Web3::Eth::Contract.new abi, @web3_rpc
38
+ end
39
+
40
+ def load_contract etherscan_api, contract_address
41
+ contract(etherscan_api.contract_getabi address: contract_address).at contract_address
42
+ end
43
+
44
+
45
+ def method_missing m, *args
46
+ @web3_rpc.request "#{PREFIX}#{m}", args[0]
47
+ end
48
+
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,71 @@
1
+ module Web3
2
+ module Eth
3
+ class Etherscan
4
+
5
+
6
+ DEFAULT_CONNECT_OPTIONS = {
7
+ open_timeout: 10,
8
+ read_timeout: 70,
9
+ parse_result: true,
10
+ url: 'https://api.etherscan.io/api'
11
+ }
12
+
13
+ attr_reader :api_key, :connect_options
14
+
15
+ def initialize api_key, connect_options: DEFAULT_CONNECT_OPTIONS
16
+ @api_key = api_key
17
+ @connect_options = connect_options
18
+ end
19
+
20
+ def method_missing m, *args
21
+ api_module, action = m.to_s.split '_', 2
22
+ raise "Calling method must be in form <module>_<action>" unless action
23
+
24
+ arguments = args[0].kind_of?(String) ? { address: args[0] } : args[0]
25
+ result = request api_module, action, arguments
26
+
27
+ if connect_options[:parse_result]
28
+ begin
29
+ JSON.parse result
30
+ rescue
31
+ result
32
+ end
33
+ else
34
+ result
35
+ end
36
+
37
+ end
38
+
39
+
40
+ private
41
+
42
+ def request api_module, action, args = {}
43
+
44
+ uri = URI connect_options[:url]
45
+ uri.query = URI.encode_www_form({
46
+ module: api_module,
47
+ action: action,
48
+ apikey: api_key
49
+ }.merge(args))
50
+
51
+ Net::HTTP.start(uri.host, uri.port,
52
+ connect_options.merge(use_ssl: uri.scheme=='https' )) do |http|
53
+
54
+ request = Net::HTTP::Get.new uri
55
+ response = http.request request
56
+
57
+ raise "Error code #{response.code} on request #{uri.to_s} #{request.body}" unless response.kind_of? Net::HTTPOK
58
+
59
+ json = JSON.parse(response.body)
60
+
61
+ raise "Response #{json['message']} on request #{uri.to_s}" unless json['status']=='1'
62
+
63
+ json['result']
64
+
65
+ end
66
+ end
67
+
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ module Web3
2
+ module Eth
3
+
4
+ class Log
5
+
6
+ attr_reader :raw_data
7
+
8
+ def initialize log
9
+ @raw_data = log
10
+
11
+ log.each do |k, v|
12
+ self.instance_variable_set("@#{k}", v)
13
+ self.class.send(:define_method, k, proc {self.instance_variable_get("@#{k}")})
14
+ end
15
+
16
+ end
17
+
18
+ def has_topics?
19
+ !!topics.first
20
+ end
21
+
22
+ def method_hash
23
+ topics.first && topics.first[2..65]
24
+ end
25
+
26
+ def indexed_args
27
+ topics[1...topics.size].collect{|x| x[2..65]}
28
+ end
29
+
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,71 @@
1
+ module Web3
2
+ module Eth
3
+
4
+ class Rpc
5
+
6
+ require 'json'
7
+ require 'net/http'
8
+
9
+ JSON_RPC_VERSION = '2.0'
10
+
11
+ DEFAULT_CONNECT_OPTIONS = {
12
+ use_ssl: false,
13
+ open_timeout: 10,
14
+ read_timeout: 70
15
+ }
16
+
17
+ DEFAULT_HOST = 'localhost'
18
+ DEFAULT_PORT = 8545
19
+
20
+ attr_reader :eth, :trace
21
+
22
+ def initialize host: DEFAULT_HOST, port: DEFAULT_PORT, connect_options: DEFAULT_CONNECT_OPTIONS
23
+
24
+ @client_id = Random.rand 10000000
25
+
26
+ @uri = URI((connect_options[:use_ssl] ? 'https' : 'http')+ "://#{host}:#{port}#{connect_options[:rpc_path]}")
27
+ @connect_options = connect_options
28
+
29
+ @eth = EthModule.new self
30
+ @trace = TraceModule.new self
31
+
32
+ end
33
+
34
+
35
+ def request method, params = nil
36
+
37
+
38
+ Net::HTTP.start(@uri.host, @uri.port, @connect_options) do |http|
39
+
40
+ request = Net::HTTP::Post.new @uri, {"Content-Type" => "application/json"}
41
+ request.body = {:jsonrpc => JSON_RPC_VERSION, method: method, params: params, id: @client_id}.compact.to_json
42
+ response = http.request request
43
+
44
+ raise "Error code #{response.code} on request #{@uri.to_s} #{request.body}" unless response.kind_of? Net::HTTPOK
45
+
46
+ body = JSON.parse(response.body)
47
+
48
+ if body['result']
49
+ body['result']
50
+ elsif body['error']
51
+ raise "Error #{@uri.to_s} #{body['error']} on request #{@uri.to_s} #{request.body}"
52
+ else
53
+ raise "No response on request #{@uri.to_s} #{request.body}"
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+
61
+ end
62
+ end
63
+ end
64
+
65
+ unless Hash.method_defined?(:compact)
66
+ class Hash
67
+ def compact
68
+ self.reject{ |_k, v| v.nil? }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,26 @@
1
+ module Web3
2
+ module Eth
3
+
4
+ class TraceModule
5
+
6
+ include Web3::Eth::Utility
7
+
8
+ PREFIX = 'trace_'
9
+
10
+ def initialize web3_rpc
11
+ @web3_rpc = web3_rpc
12
+ end
13
+
14
+ def method_missing m, *args
15
+ @web3_rpc.request "#{PREFIX}#{m}", args[0]
16
+ end
17
+
18
+ def internalCallsByHash tx_hash
19
+ @web3_rpc.request("#{PREFIX}transaction", [tx_hash]).select{|t| t['traceAddress']!=[]}.collect{|t|
20
+ CallTrace.new t
21
+ }
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,73 @@
1
+ module Web3
2
+ module Eth
3
+
4
+ class Transaction
5
+
6
+ include Web3::Eth::Utility
7
+
8
+ attr_reader :raw_data
9
+
10
+ def initialize transaction_data
11
+ @raw_data = transaction_data
12
+ transaction_data.each do |k, v|
13
+ self.instance_variable_set("@#{k}", v)
14
+ self.class.send(:define_method, k, proc {self.instance_variable_get("@#{k}")})
15
+ end
16
+ end
17
+
18
+ def method_hash
19
+ if input && input.length>=10
20
+ input[2...10]
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ # suffix # 0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
27
+ # look http://solidity.readthedocs.io/en/latest/metadata.html for details
28
+ def call_input_data
29
+ if raw_data['creates'] && input
30
+ fetch_constructor_data input
31
+ elsif input && input.length>10
32
+ input[10..input.length]
33
+ else
34
+ []
35
+ end
36
+ end
37
+
38
+ def block_number
39
+ # if transaction is less than 12 seconds old, blockNumber will be nil
40
+ # :. nil check before calling `to_hex` to avoid argument error
41
+ blockNumber && from_hex(blockNumber)
42
+ end
43
+
44
+ def value_wei
45
+ from_hex value
46
+ end
47
+
48
+ def value_eth
49
+ wei_to_ether from_hex value
50
+ end
51
+
52
+ def gas_limit
53
+ from_hex gas
54
+ end
55
+
56
+ def gasPrice_eth
57
+ wei_to_ether from_hex gasPrice
58
+ end
59
+
60
+ private
61
+
62
+ CONSTRUCTOR_SEQ = /a165627a7a72305820\w{64}0029(\w*)$/
63
+ def fetch_constructor_data input
64
+ data = input[CONSTRUCTOR_SEQ,1]
65
+ while data && (d = data[CONSTRUCTOR_SEQ,1])
66
+ data = d
67
+ end
68
+ data
69
+ end
70
+
71
+ end
72
+ end
73
+ end