evm_client 0.1.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.
- 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
|