evm_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +32 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +21 -0
- data/PREREQUISITES.md +75 -0
- data/README.md +665 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/install_parity +22 -0
- data/bin/setup +7 -0
- data/contracts/AccountingLib.sol +112 -0
- data/contracts/AuditorInterface.sol +4 -0
- data/contracts/AuditorRegistry.sol +14 -0
- data/contracts/CustodianInterface.sol +27 -0
- data/contracts/CustodianRegistry.sol +40 -0
- data/contracts/DigixConfiguration.sol +68 -0
- data/contracts/Directory.sol +67 -0
- data/contracts/DoublyLinked.sol +54 -0
- data/contracts/GenericInterface.sol +56 -0
- data/contracts/GenericRegistry.sol +15 -0
- data/contracts/Gold.sol +105 -0
- data/contracts/GoldRegistry.sol +82 -0
- data/contracts/GoldTokenLedger.sol +3 -0
- data/contracts/Interface.sol +27 -0
- data/contracts/Minter.sol +3 -0
- data/contracts/Recaster.sol +3 -0
- data/contracts/Testing.sol +59 -0
- data/contracts/VendorInterface.sol +82 -0
- data/contracts/VendorRegistry.sol +39 -0
- data/contracts/classic/Digixbot.sol +106 -0
- data/contracts/classic/DigixbotConfiguration.sol +62 -0
- data/contracts/classic/DigixbotEthereum.sol +86 -0
- data/contracts/classic/DigixbotUsers.sol +103 -0
- data/contracts/classic/Gold.sol +497 -0
- data/contracts/classic/GoldRegistry.sol +503 -0
- data/contracts/classic/GoldTokenLedger.sol +560 -0
- data/contracts/classic/GoldTokenMinter.sol +607 -0
- data/contracts/classic/ParticipantRegistry.sol +94 -0
- data/contracts/classic/QueueSample.sol +54 -0
- data/evm_client.gemspec +36 -0
- data/lib/evm_client.rb +15 -0
- data/lib/evm_client/abi.rb +32 -0
- data/lib/evm_client/client.rb +146 -0
- data/lib/evm_client/contract.rb +341 -0
- data/lib/evm_client/contract_event.rb +32 -0
- data/lib/evm_client/contract_initializer.rb +54 -0
- data/lib/evm_client/decoder.rb +99 -0
- data/lib/evm_client/deployment.rb +49 -0
- data/lib/evm_client/encoder.rb +118 -0
- data/lib/evm_client/event_log.rb +88 -0
- data/lib/evm_client/explorer_url_helper.rb +25 -0
- data/lib/evm_client/formatter.rb +146 -0
- data/lib/evm_client/function.rb +40 -0
- data/lib/evm_client/function_input.rb +14 -0
- data/lib/evm_client/function_output.rb +14 -0
- data/lib/evm_client/http_client.rb +44 -0
- data/lib/evm_client/initializer.rb +27 -0
- data/lib/evm_client/ipc_client.rb +57 -0
- data/lib/evm_client/project_initializer.rb +28 -0
- data/lib/evm_client/railtie.rb +12 -0
- data/lib/evm_client/singleton.rb +39 -0
- data/lib/evm_client/solidity.rb +40 -0
- data/lib/evm_client/transaction.rb +41 -0
- data/lib/evm_client/version.rb +3 -0
- data/lib/tasks/ethereum_contract.rake +27 -0
- data/lib/tasks/ethereum_node.rake +52 -0
- data/lib/tasks/ethereum_test.rake +32 -0
- data/lib/tasks/ethereum_transaction.rake +24 -0
- metadata +219 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module EvmClient
|
2
|
+
module ExplorerUrlHelper
|
3
|
+
|
4
|
+
CHAIN_PREFIX = {
|
5
|
+
17 => "no-explorer-for-devmode.",
|
6
|
+
42 => "kovan.",
|
7
|
+
3 => "ropsten.",
|
8
|
+
4 => "rinkeby.",
|
9
|
+
5 => "goerli."
|
10
|
+
}
|
11
|
+
|
12
|
+
def link_to_tx(label, txid, **opts)
|
13
|
+
link_to label, explorer_path("tx/#{txid}"), {target: "_blank"}.merge(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def link_to_address(label, address, **opts)
|
17
|
+
link_to label, explorer_path("address/#{address}"), {target: "_blank"}.merge(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def explorer_path(suffix, version = EvmClient::Singleton.instance.get_chain)
|
21
|
+
prefix = CHAIN_PREFIX.fetch(version, "")
|
22
|
+
"https://#{prefix}etherscan.io/#{suffix}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module EvmClient
|
2
|
+
class Formatter
|
3
|
+
|
4
|
+
UNITS = {
|
5
|
+
wei: 1,
|
6
|
+
kwei: 1000,
|
7
|
+
ada: 1000,
|
8
|
+
femtoether: 1000,
|
9
|
+
mwei: 1000000,
|
10
|
+
babbage: 1000000,
|
11
|
+
picoether: 1000000,
|
12
|
+
gwei: 1000000000,
|
13
|
+
shannon: 1000000000,
|
14
|
+
nanoether: 1000000000,
|
15
|
+
nano: 1000000000,
|
16
|
+
szabo: 1000000000000,
|
17
|
+
microether: 1000000000000,
|
18
|
+
micro: 1000000000000,
|
19
|
+
finney: 1000000000000000,
|
20
|
+
milliether: 1000000000000000,
|
21
|
+
milli: 1000000000000000,
|
22
|
+
ether: 1000000000000000000,
|
23
|
+
eth: 1000000000000000000,
|
24
|
+
kether: 1000000000000000000000,
|
25
|
+
grand: 1000000000000000000000,
|
26
|
+
einstein: 1000000000000000000000,
|
27
|
+
mether: 1000000000000000000000000,
|
28
|
+
gether: 1000000000000000000000000000,
|
29
|
+
tether: 1000000000000000000000000000000
|
30
|
+
}
|
31
|
+
|
32
|
+
def valid_address?(address_string)
|
33
|
+
address = address_string.gsub(/^0x/,'')
|
34
|
+
return false if address == "0000000000000000000000000000000000000000"
|
35
|
+
return false if address.length != 40
|
36
|
+
return !(address.match(/[0-9a-fA-F]+/).nil?)
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_bool(boolval)
|
40
|
+
return nil if boolval.nil?
|
41
|
+
boolval ? "1" : "0"
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_bool(hexstring)
|
45
|
+
return nil if hexstring.nil?
|
46
|
+
(hexstring == "0000000000000000000000000000000000000000000000000000000000000001")
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_ascii(hexstring)
|
50
|
+
return nil if hexstring.nil?
|
51
|
+
hexstring.gsub(/^0x/,'').scan(/.{2}/).collect {|x| x.hex}.pack("c*")
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_utf8(hexstring)
|
55
|
+
return nil if hexstring.nil?
|
56
|
+
hexstring.gsub(/^0x/,'').scan(/.{2}/).collect {|x| x.hex}.pack("U*").delete("\u0000")
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_ascii(ascii_string)
|
60
|
+
return nil if ascii_string.nil?
|
61
|
+
ascii_string.unpack('H*')[0]
|
62
|
+
end
|
63
|
+
|
64
|
+
def from_utf8(utf8_string)
|
65
|
+
return nil if utf8_string.nil?
|
66
|
+
utf8_string.force_encoding('UTF-8').split("").collect {|x| x.ord.to_s(16).rjust(2, '0')}.join("")
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_address(hexstring)
|
70
|
+
return "0x0000000000000000000000000000000000000000" if hexstring.nil?
|
71
|
+
"0x" + hexstring[-40..-1]
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_wei(amount, unit = "ether")
|
75
|
+
return nil if amount.nil?
|
76
|
+
BigDecimal(UNITS[unit.to_sym] * amount, 16).to_s.to_i rescue nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def from_wei(amount, unit = "ether")
|
80
|
+
return nil if amount.nil?
|
81
|
+
(BigDecimal(amount, 16) / BigDecimal(UNITS[unit.to_sym], 16)).to_s rescue nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def from_address(address)
|
85
|
+
return "0x0000000000000000000000000000000000000000" if address.nil?
|
86
|
+
address.gsub(/^0x/,'').rjust(64, "0")
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_param(string)
|
90
|
+
string.ljust(64, '0')
|
91
|
+
end
|
92
|
+
|
93
|
+
def from_input(string)
|
94
|
+
string[10..-1].scan(/.{64}/)
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_twos_complement(number)
|
98
|
+
(number & ((1 << 256) - 1)).to_s(16)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_int(hexstring)
|
102
|
+
return nil if hexstring.nil?
|
103
|
+
(hexstring.gsub(/^0x/,'')[0..1] == "ff") ? (hexstring.hex - (2 ** 256)) : hexstring.hex
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_base_type(typename)
|
107
|
+
typename.gsub(/\d+/,'')
|
108
|
+
end
|
109
|
+
|
110
|
+
def from_payload(args)
|
111
|
+
converter = "output_to_#{self.get_base_type(args[0])}".to_sym
|
112
|
+
self.send(converter, args[1])
|
113
|
+
end
|
114
|
+
|
115
|
+
def output_to_address(bytes)
|
116
|
+
self.to_address(bytes)
|
117
|
+
end
|
118
|
+
|
119
|
+
def output_to_bytes(bytes)
|
120
|
+
self.to_utf8(bytes)
|
121
|
+
end
|
122
|
+
|
123
|
+
def output_to_string(bytes)
|
124
|
+
self.to_utf8(bytes)
|
125
|
+
end
|
126
|
+
|
127
|
+
def output_to_uint(bytes)
|
128
|
+
self.to_int(bytes)
|
129
|
+
end
|
130
|
+
|
131
|
+
def output_to_int(bytes)
|
132
|
+
self.to_int(bytes)
|
133
|
+
end
|
134
|
+
|
135
|
+
def output_to_bool(bytes)
|
136
|
+
self.to_bool(bytes.gsub(/^0x/,''))
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_output(args)
|
140
|
+
converter = "output_to_#{self.get_base_type(args[0])}".to_sym
|
141
|
+
self.send(converter, args[1])
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module EvmClient
|
2
|
+
class Function
|
3
|
+
|
4
|
+
attr_accessor :name, :inputs, :outputs, :signature, :minified_signature, :constant, :function_string
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@name = data["name"]
|
8
|
+
@constant = data["constant"]
|
9
|
+
|
10
|
+
@inputs = data["inputs"].map do |input|
|
11
|
+
EvmClient::FunctionInput.new(input)
|
12
|
+
end
|
13
|
+
|
14
|
+
@outputs = data["outputs"].collect do |output|
|
15
|
+
EvmClient::FunctionOutput.new(output)
|
16
|
+
end
|
17
|
+
|
18
|
+
@function_string = self.class.calc_signature(@name, @inputs)
|
19
|
+
@signature = self.class.calc_id(@function_string)
|
20
|
+
@minified_signature = signature[0..7]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.to_canonical_type(type)
|
24
|
+
type
|
25
|
+
.gsub(/(int)(\z|\D)/, '\1256\2')
|
26
|
+
.gsub(/(uint)(\z|\D)/, '\1256\2')
|
27
|
+
.gsub(/(fixed)(\z|\D)/, '\1128x128\2')
|
28
|
+
.gsub(/(ufixed)(\z|\D)/, '\1128x128\2')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.calc_signature(name, inputs)
|
32
|
+
"#{name}(#{inputs.collect {|x| self.to_canonical_type(x.type) }.join(",")})"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.calc_id(signature)
|
36
|
+
Digest::SHA3.hexdigest(signature, 256)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
module EvmClient
|
4
|
+
class HttpClient < Client
|
5
|
+
attr_accessor :host, :port, :uri, :ssl, :proxy
|
6
|
+
|
7
|
+
def initialize(host, proxy = nil, log = false)
|
8
|
+
super(log)
|
9
|
+
uri = URI.parse(host)
|
10
|
+
raise ArgumentError unless ['http', 'https'].include? uri.scheme
|
11
|
+
@host = uri.host
|
12
|
+
@port = uri.port
|
13
|
+
@proxy = proxy
|
14
|
+
|
15
|
+
@ssl = uri.scheme == 'https'
|
16
|
+
@uri = URI("#{uri.scheme}://#{@host}:#{@port}#{uri.path}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_single(payload)
|
20
|
+
if @proxy.present?
|
21
|
+
_, p_username, p_password, p_host, p_port = @proxy.gsub(/(:|\/|@)/,' ').squeeze(' ').split
|
22
|
+
http = ::Net::HTTP.new(@host, @port, p_host, p_port, p_username, p_password)
|
23
|
+
else
|
24
|
+
http = ::Net::HTTP.new(@host, @port)
|
25
|
+
end
|
26
|
+
|
27
|
+
if @ssl
|
28
|
+
http.use_ssl = true
|
29
|
+
end
|
30
|
+
header = {'Content-Type' => 'application/json'}
|
31
|
+
request = ::Net::HTTP::Post.new(uri, header)
|
32
|
+
request.body = payload
|
33
|
+
response = http.request(request)
|
34
|
+
response.body
|
35
|
+
end
|
36
|
+
|
37
|
+
def send_batch(batch)
|
38
|
+
result = send_single(batch.to_json)
|
39
|
+
result = JSON.parse(result)
|
40
|
+
result.sort_by! { |c| c['id'] }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module EvmClient
|
2
|
+
|
3
|
+
class Initializer
|
4
|
+
attr_accessor :contracts, :file, :client
|
5
|
+
|
6
|
+
def initialize(file, client = EvmClient::Singleton.instance)
|
7
|
+
@client = client
|
8
|
+
sol_output = Solidity.new.compile(file)
|
9
|
+
contracts = sol_output.keys
|
10
|
+
|
11
|
+
@contracts = []
|
12
|
+
contracts.each do |contract|
|
13
|
+
abi = JSON.parse(sol_output[contract]["abi"] )
|
14
|
+
name = contract
|
15
|
+
code = sol_output[contract]["bin"]
|
16
|
+
@contracts << Contract.new(name, code, abi, @client)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_all
|
21
|
+
@contracts.each do |contract|
|
22
|
+
contract.build
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'socket'
|
2
|
+
module EvmClient
|
3
|
+
class IpcClient < Client
|
4
|
+
attr_accessor :ipcpath
|
5
|
+
|
6
|
+
IPC_PATHS = [
|
7
|
+
"#{ENV['HOME']}/.parity/jsonrpc.ipc",
|
8
|
+
"#{ENV['HOME']}/.openethereum/jsonrpc.ipc",
|
9
|
+
"#{ENV['HOME']}/Library/Ethereum/geth.ipc",
|
10
|
+
"#{ENV['HOME']}/Library/Ethereum/testnet/geth.ipc",
|
11
|
+
"#{ENV['HOME']}/Library/Application\ Support/io.parity.ethereum/jsonrpc.ipc",
|
12
|
+
"#{ENV['HOME']}/Library/Application\ Support/io.openethereum.ethereum/jsonrpc.ipc",
|
13
|
+
"#{ENV['HOME']}/.local/share/parity/jsonrpc.ipc",
|
14
|
+
"#{ENV['HOME']}/.local/share/io.parity.ethereum/jsonrpc.ipc",
|
15
|
+
"#{ENV['HOME']}/AppData/Roaming/Parity/Ethereum/jsonrpc.ipc",
|
16
|
+
"#{ENV['HOME']}/.local/share/openethereum/jsonrpc.ipc",
|
17
|
+
"#{ENV['HOME']}/.local/share/io.openethereum.ethereum/jsonrpc.ipc",
|
18
|
+
"#{ENV['HOME']}/AppData/Roaming/openethereum/Ethereum/jsonrpc.ipc",
|
19
|
+
"#{ENV['HOME']}/.ethereum/geth.ipc",
|
20
|
+
"#{ENV['HOME']}/.ethereum/testnet/geth.ipc"
|
21
|
+
]
|
22
|
+
|
23
|
+
def initialize(ipcpath = nil, log = true)
|
24
|
+
super(log)
|
25
|
+
ipcpath ||= IpcClient.default_path
|
26
|
+
@ipcpath = ipcpath
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.default_path(paths = IPC_PATHS)
|
30
|
+
path = paths.find { |path| File.exist?(path) }
|
31
|
+
path || raise("Ipc file not found. Please pass in the file path explicitly to IpcClient initializer")
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_single(payload)
|
35
|
+
socket = UNIXSocket.new(@ipcpath)
|
36
|
+
socket.puts(payload)
|
37
|
+
read = socket.recvmsg(nil)[0]
|
38
|
+
socket.close
|
39
|
+
return read
|
40
|
+
end
|
41
|
+
|
42
|
+
# Note: Guarantees the results are in the same order as defined in batch call.
|
43
|
+
# client.batch do
|
44
|
+
# client.eth_block_number
|
45
|
+
# client.eth_mining
|
46
|
+
# end
|
47
|
+
# => [{"jsonrpc"=>"2.0", "id"=>1, "result"=>"0x26"}, {"jsonrpc"=>"2.0", "id"=>2, "result"=>false}]
|
48
|
+
def send_batch(batch)
|
49
|
+
result = send_single(batch.to_json)
|
50
|
+
result = JSON.parse(result)
|
51
|
+
|
52
|
+
# Make sure the order is the same as it was when batching calls
|
53
|
+
# See 6 Batch here http://www.jsonrpc.org/specification
|
54
|
+
return result.sort_by! { |c| c['id'] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EvmClient
|
2
|
+
|
3
|
+
class ProjectInitializer
|
4
|
+
|
5
|
+
attr_accessor :contract_names, :combined_output, :contracts, :libraries
|
6
|
+
|
7
|
+
def initialize(location, optimize = false)
|
8
|
+
ENV['ETHEREUM_SOLIDITY_BINARY'] ||= "/usr/local/bin/solc"
|
9
|
+
solidity = ENV['ETHEREUM_SOLIDITY_BINARY']
|
10
|
+
contract_dir = location
|
11
|
+
if optimize
|
12
|
+
opt_flag = "--optimize"
|
13
|
+
else
|
14
|
+
opt_flag = ""
|
15
|
+
end
|
16
|
+
compile_command = "#{solidity} #{opt_flag} --combined-json abi,bin #{contract_dir}"
|
17
|
+
raw_data = `#{compile_command}`
|
18
|
+
data = JSON.parse(raw_data)
|
19
|
+
@contract_names = data["contracts"].keys
|
20
|
+
@libraries = {}
|
21
|
+
@contracts = @contract_names.collect do |contract_name|
|
22
|
+
ContractInitializer.new(contract_name, data["contracts"][contract_name], self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class EvmClient::Singleton
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
attr_accessor :client, :ipcpath, :host, :log, :instance, :default_account
|
6
|
+
|
7
|
+
def instance
|
8
|
+
@instance ||= configure_instance(create_instance)
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup
|
12
|
+
yield(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@instance = nil
|
17
|
+
@client = nil
|
18
|
+
@host = nil
|
19
|
+
@log = nil
|
20
|
+
@ipcpath = nil
|
21
|
+
@default_account = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def create_instance
|
26
|
+
return EvmClient::IpcClient.new(@ipcpath) if @client == :ipc
|
27
|
+
return EvmClient::HttpClient.new(@host) if @client == :http
|
28
|
+
EvmClient::IpcClient.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure_instance(instance)
|
32
|
+
instance.tap do |i|
|
33
|
+
i.default_account = @default_account if @default_account.present?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|