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,32 @@
|
|
1
|
+
module EvmClient
|
2
|
+
class ContractEvent
|
3
|
+
|
4
|
+
attr_accessor :name, :signature, :input_types, :inputs,
|
5
|
+
:event_string, :address, :client,
|
6
|
+
:indexed_inputs, :indexed_outputs,
|
7
|
+
:non_indexed_inputs, :non_indexed_outputs
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@name = data['name']
|
11
|
+
@input_types = data['inputs'].map {|x| x['type']}
|
12
|
+
@inputs = data['inputs'].map {|x| x['name']}
|
13
|
+
@event_string = "#{@name}(#{@input_types.join(",")})"
|
14
|
+
@signature = Digest::SHA3.hexdigest(@event_string, 256)
|
15
|
+
|
16
|
+
@indexed_inputs = Array(data['inputs']).select { |input| input['indexed'] == true }
|
17
|
+
@non_indexed_inputs = Array(data['inputs']).select { |input| input['indexed'] == false }
|
18
|
+
|
19
|
+
@indexed_outputs = Array(data['outputs']).select { |output| output['indexed'] == true }
|
20
|
+
@non_indexed_outputs = Array(data['outputs']).select { |output| output['indexed'] == false }
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_address(address)
|
24
|
+
@address = address
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_client(client)
|
28
|
+
@client = client
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module EvmClient
|
2
|
+
|
3
|
+
class ContractInitializer
|
4
|
+
|
5
|
+
attr_accessor :abi, :binary, :name, :libraries, :needs_linking, :project_initializer, :contract
|
6
|
+
|
7
|
+
def initialize(contract_name, contract, project_initializer)
|
8
|
+
@abi = JSON.parse(contract["abi"]) unless contract.nil?
|
9
|
+
@binary = contract["bin"] unless contract.nil?
|
10
|
+
@name = contract_name
|
11
|
+
@project_initializer = project_initializer
|
12
|
+
matchdata = @binary.scan(/_+[a-zA-Z]+_+/).uniq
|
13
|
+
@needs_linking = matchdata.present?
|
14
|
+
if @needs_linking
|
15
|
+
@libraries = matchdata.collect do |libname|
|
16
|
+
{name: libname.gsub(/_+/,''), sigil: libname}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def link_libraries
|
22
|
+
if @needs_linking
|
23
|
+
@libraries.each do |library|
|
24
|
+
name = library[:name]
|
25
|
+
if @project_initializer.libraries[name].nil?
|
26
|
+
ENV['ETHEREUM_DEPLOYER_WAIT_TIME'] ||= "120"
|
27
|
+
wait_time = ENV['ETHEREUM_DEPLOYER_WAIT_TIME'].to_i
|
28
|
+
library_instance = library[:name].constantize.new
|
29
|
+
puts "Deploying library #{name}"
|
30
|
+
library_instance.deploy_and_wait(wait_time)
|
31
|
+
puts "Library deployed at #{library_instance.address}"
|
32
|
+
@project_initializer.libraries[name] = library_instance.address
|
33
|
+
@binary.gsub!(library[:sigil], library_instance.address.gsub(/^0x/,''))
|
34
|
+
else
|
35
|
+
address = @project_initializer.libraries[name]
|
36
|
+
@binary.gsub!(library[:sigil], address.gsub(/^0x/,''))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def build(connection)
|
43
|
+
@contract = EvmClient::Contract.new(@name, @binary, @abi, client)
|
44
|
+
@contract.build(connection)
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_javascripts(path)
|
48
|
+
data = {name: @name, abi: @abi, binary: @binary}
|
49
|
+
File.open(File.join(path, "#{@name}.json"), 'w') {|f| f.puts data.to_json}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module EvmClient
|
2
|
+
class Decoder
|
3
|
+
|
4
|
+
def decode(type, value, start = 0)
|
5
|
+
is_array, arity, array_subtype = Abi::parse_array_type(type)
|
6
|
+
if is_array && arity
|
7
|
+
decode_static_array(arity, array_subtype, value, start)
|
8
|
+
elsif is_array
|
9
|
+
decode_dynamic_array(array_subtype, value, start)
|
10
|
+
else
|
11
|
+
value = value.gsub(/^0x/,'')
|
12
|
+
core, subtype = Abi::parse_type(type)
|
13
|
+
method_name = "decode_#{core}".to_sym
|
14
|
+
self.send(method_name, value, subtype, start)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def decode_static_array(arity, array_subtype, value, start)
|
19
|
+
(0..arity-1).map { |i| decode(array_subtype, value, start + i * 64) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode_dynamic_array(array_subtype, value, start)
|
23
|
+
location = decode_uint(value[start..(start+63)]) * 2
|
24
|
+
size = decode_uint(value[location..location+63])
|
25
|
+
(0..size-1).map { |i| decode(array_subtype, value, location + (i+1) * 64) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def decode_fixed(value, subtype = "128x128", start = 0)
|
29
|
+
decode_int(trim(value, start, fixed_bitsize(subtype))).to_f / 2**exponent(subtype)
|
30
|
+
end
|
31
|
+
|
32
|
+
def decode_uint(value, subtype = "256", start = 0)
|
33
|
+
trim(value, start, bitsize(subtype)).hex
|
34
|
+
end
|
35
|
+
|
36
|
+
def decode_int(value, subtype = "256", start = 0)
|
37
|
+
raise ArgumentError if value.nil?
|
38
|
+
size = bitsize(subtype)
|
39
|
+
value = trim(value, start, size)
|
40
|
+
(value[0..1] == "ff") ? (value.hex - (2 ** size)) : value.hex
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode_bool(value, _, start)
|
44
|
+
value = trim(value, start, 4)
|
45
|
+
return true if value == "1"
|
46
|
+
return false if value == "0"
|
47
|
+
raise ArgumentError
|
48
|
+
end
|
49
|
+
|
50
|
+
def decode_address(value, _ = nil, start)
|
51
|
+
raise ArgumentError if value.size-start < 64
|
52
|
+
value[start+24..start+63]
|
53
|
+
end
|
54
|
+
|
55
|
+
def decode_bytes(value, subtype, start)
|
56
|
+
subtype.present? ? decode_static_bytes(value, subtype, start) : decode_dynamic_bytes(value, start)
|
57
|
+
end
|
58
|
+
|
59
|
+
def decode_static_bytes(value, subtype = nil, start = 0)
|
60
|
+
trim(value, start, subtype.to_i*8).scan(/.{2}/).collect {|x| x.hex}.pack('C*').strip
|
61
|
+
end
|
62
|
+
|
63
|
+
def decode_dynamic_bytes(value, start = 0)
|
64
|
+
location = decode_uint(value[start..(start+63)]) * 2
|
65
|
+
size = decode_uint(value[location..location+63]) * 2
|
66
|
+
value[location+64..location+63+size].scan(/.{2}/).collect {|x| x.hex}.pack('C*')
|
67
|
+
end
|
68
|
+
|
69
|
+
def decode_string(value, _ = nil, start = 0)
|
70
|
+
decode_dynamic_bytes(value, start).force_encoding('utf-8')
|
71
|
+
end
|
72
|
+
|
73
|
+
def decode_arguments(arguments, data)
|
74
|
+
data = data.gsub(/^0x/,'')
|
75
|
+
types = arguments.map { |o| o.type }
|
76
|
+
types.each.with_index.map { |t , i| decode(t, data, i*64) }
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def trim(value, start, bitsize = 256)
|
81
|
+
value[start+63-(bitsize/4-1)..start+63]
|
82
|
+
end
|
83
|
+
|
84
|
+
def bitsize(subtype, default = 256)
|
85
|
+
subtype.present? ? subtype.to_i : default
|
86
|
+
end
|
87
|
+
|
88
|
+
def fixed_bitsize(subtype = nil)
|
89
|
+
subtype ||= "128x128"
|
90
|
+
_, x, n = /(\d+)x(\d+)/.match(subtype).to_a
|
91
|
+
x.to_i + n.to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
def exponent(subtype, default = 128)
|
95
|
+
subtype.nil? ? default : /(\d+)x(\d+)/.match(subtype)[2].to_i
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module EvmClient
|
2
|
+
|
3
|
+
class Deployment
|
4
|
+
|
5
|
+
DEFAULT_TIMEOUT = 300.seconds
|
6
|
+
DEFAULT_STEP = 5.seconds
|
7
|
+
|
8
|
+
attr_accessor :id, :contract_address, :connection, :deployed, :mined
|
9
|
+
attr_reader :valid_deployment
|
10
|
+
|
11
|
+
def initialize(txid, connection)
|
12
|
+
@id = txid
|
13
|
+
@connection = connection
|
14
|
+
@deployed = false
|
15
|
+
@contract_address = nil
|
16
|
+
@valid_deployment = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def mined?
|
20
|
+
return true if @mined
|
21
|
+
@mined = @connection.eth_get_transaction_by_hash(@id)["result"]["blockNumber"].present?
|
22
|
+
@mined ||= false
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_deployed
|
26
|
+
return false unless @id
|
27
|
+
contract_receipt = @connection.eth_get_transaction_receipt(@id)
|
28
|
+
result = contract_receipt["result"]
|
29
|
+
has_contract_address = result && result["contractAddress"]
|
30
|
+
@contract_address ||= result["contractAddress"] if has_contract_address
|
31
|
+
has_contract_address && result["blockNumber"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def deployed?
|
35
|
+
@valid_deployment ||= check_deployed
|
36
|
+
end
|
37
|
+
|
38
|
+
def wait_for_deployment(timeout: DEFAULT_TIMEOUT, step: DEFAULT_STEP)
|
39
|
+
start_time = Time.now
|
40
|
+
loop do
|
41
|
+
raise "Transaction #{@id} timed out." if ((Time.now - start_time) > timeout)
|
42
|
+
yield if block_given?
|
43
|
+
return true if deployed?
|
44
|
+
sleep step
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module EvmClient
|
2
|
+
|
3
|
+
class Encoder
|
4
|
+
|
5
|
+
def encode(type, value)
|
6
|
+
is_array, arity, array_subtype = Abi::parse_array_type(type)
|
7
|
+
if is_array && arity
|
8
|
+
encode_static_array(arity, array_subtype, value)
|
9
|
+
elsif is_array
|
10
|
+
encode_dynamic_array(array_subtype, value)
|
11
|
+
else
|
12
|
+
core, subtype = Abi::parse_type(type)
|
13
|
+
method_name = "encode_#{core}".to_sym
|
14
|
+
self.send(method_name, value, subtype)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def encode_static_array(arity, array_subtype, array)
|
19
|
+
raise "Wrong number of arguments" if arity != array.size
|
20
|
+
array.inject("") { |a, e| a << encode(array_subtype, e) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def encode_dynamic_array(array_subtype, array)
|
24
|
+
location = encode_uint(@inputs ? size_of_inputs(@inputs) + @tail.size/2 : 32)
|
25
|
+
size = encode_uint(array.size)
|
26
|
+
data = array.inject("") { |a, e| a << encode(array_subtype, e) }
|
27
|
+
[location, size + data]
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode_int(value, _ = nil)
|
31
|
+
to_twos_complement(value).to_s(16).rjust(64, '0')
|
32
|
+
end
|
33
|
+
|
34
|
+
def encode_uint(value, _ = nil)
|
35
|
+
raise ArgumentError if value < 0
|
36
|
+
encode_int(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def encode_bool(value, _)
|
40
|
+
(value ? "1" : "0").rjust(64, '0')
|
41
|
+
end
|
42
|
+
|
43
|
+
def encode_fixed(value, subtype)
|
44
|
+
n = subtype.nil? ? 128 : /(\d+)x(\d+)/.match(subtype)[2].to_i
|
45
|
+
do_encode_fixed(value, n)
|
46
|
+
end
|
47
|
+
|
48
|
+
def do_encode_fixed(value, n)
|
49
|
+
encode_uint((value * 2**n).to_i)
|
50
|
+
end
|
51
|
+
|
52
|
+
def encode_ufixed(_value, _)
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
def encode_bytes(value, subtype)
|
57
|
+
subtype.nil? ? encode_dynamic_bytes(value) : encode_static_bytes(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def encode_static_bytes(value)
|
61
|
+
value.bytes.map {|x| x.to_s(16).rjust(2, '0')}.join("").ljust(64, '0')
|
62
|
+
end
|
63
|
+
|
64
|
+
def encode_dynamic_bytes(value)
|
65
|
+
location = encode_uint(@inputs ? size_of_inputs(@inputs) + @tail.size/2 : 32)
|
66
|
+
size = encode_uint(value.size)
|
67
|
+
content = encode_static_bytes(value)
|
68
|
+
[location, size + content]
|
69
|
+
end
|
70
|
+
|
71
|
+
def encode_string(value, _)
|
72
|
+
location = encode_uint(@inputs ? size_of_inputs(@inputs) + @tail.size/2 : 32)
|
73
|
+
size = encode_uint(value.bytes.size)
|
74
|
+
content = value.bytes.map {|x| x.to_s(16).rjust(2, '0')}.join("").ljust(64, '0')
|
75
|
+
[location, size + content]
|
76
|
+
end
|
77
|
+
|
78
|
+
def encode_address(value, _)
|
79
|
+
value = "0" * 24 + value.gsub(/^0x/,'')
|
80
|
+
raise ArgumentError if value.size != 64
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
def ensure_prefix(value)
|
85
|
+
value.start_with?("0x") ? value : ("0x" + value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def encode_arguments(inputs, args)
|
89
|
+
raise "Wrong number of arguments" if inputs.length != args.length
|
90
|
+
@head = ""
|
91
|
+
@tail = ""
|
92
|
+
@inputs = inputs
|
93
|
+
inputs.each.with_index do |input, index|
|
94
|
+
encoded = encode(input.type, args[index])
|
95
|
+
if encoded.is_a? Array
|
96
|
+
@head << encoded[0]
|
97
|
+
@tail << encoded[1]
|
98
|
+
else
|
99
|
+
@head << encoded
|
100
|
+
end
|
101
|
+
end
|
102
|
+
@head + @tail
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def to_twos_complement(number)
|
107
|
+
(number & ((1 << 256) - 1))
|
108
|
+
end
|
109
|
+
|
110
|
+
def size_of_inputs(inputs)
|
111
|
+
inputs.map do |input|
|
112
|
+
_, arity, _ = Abi::parse_array_type(input.type)
|
113
|
+
arity.nil? ? 32 : arity * 32
|
114
|
+
end.inject(:+)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module EvmClient
|
2
|
+
class EventLog
|
3
|
+
attr_reader :address, :block_hash, :block_number, :data, :log_index, :removed,
|
4
|
+
:topics, :transaction_hash, :transaction_index, :signature, :contract, :event
|
5
|
+
|
6
|
+
def self.build(raw_response:, contract:)
|
7
|
+
signature = raw_response['topics'][0]
|
8
|
+
event = contract.events.find { |event| signature.match(event.signature) }
|
9
|
+
|
10
|
+
new(
|
11
|
+
address: raw_response['address'],
|
12
|
+
block_hash: raw_response['blockHash'],
|
13
|
+
block_number: raw_response['blockNumber'].to_i(16),
|
14
|
+
data: raw_response['data'],
|
15
|
+
log_index: raw_response['logIndex'],
|
16
|
+
removed: raw_response['removed'],
|
17
|
+
topics: raw_response['topics'],
|
18
|
+
transaction_hash: raw_response['transactionHash'],
|
19
|
+
transaction_index: raw_response['transactionIndex'],
|
20
|
+
signature: signature,
|
21
|
+
contract: contract,
|
22
|
+
event: event
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize( address:, block_hash:, block_number:, data:, log_index:, removed:,
|
27
|
+
topics:, transaction_hash:, transaction_index:, signature:, contract:, event:)
|
28
|
+
@address = address
|
29
|
+
@block_hash = block_hash
|
30
|
+
@block_number = block_number
|
31
|
+
@data = data
|
32
|
+
@log_index = log_index
|
33
|
+
@removed = removed
|
34
|
+
@topics = topics
|
35
|
+
@transaction_hash = transaction_hash
|
36
|
+
@transaction_index = transaction_index
|
37
|
+
@signature = signature
|
38
|
+
@contract = contract
|
39
|
+
@event = event
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
event.name
|
44
|
+
end
|
45
|
+
|
46
|
+
def return_values
|
47
|
+
return_values = {}
|
48
|
+
|
49
|
+
indexed_params.each_with_index do |param, index|
|
50
|
+
return_values[param.name] = topics[index+1]
|
51
|
+
end
|
52
|
+
|
53
|
+
non_indexed_params.each_with_index do |param, index|
|
54
|
+
return_values[param.name] = Decoder.new.decode_arguments(non_indexed_params, data)[index]
|
55
|
+
end
|
56
|
+
|
57
|
+
return_values
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_h
|
61
|
+
{
|
62
|
+
address: address,
|
63
|
+
block_hash: block_hash,
|
64
|
+
block_number: block_number,
|
65
|
+
data: data,
|
66
|
+
log_index: log_index,
|
67
|
+
removed: removed,
|
68
|
+
topics: topics,
|
69
|
+
transaction_hash: transaction_hash,
|
70
|
+
transaction_index: transaction_index,
|
71
|
+
signature: signature,
|
72
|
+
name: name,
|
73
|
+
contract_name: contract.name,
|
74
|
+
return_values: return_values
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def indexed_params
|
81
|
+
[event.indexed_inputs, event.indexed_outputs].flatten.map { |input| OpenStruct.new(input) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def non_indexed_params
|
85
|
+
[event.non_indexed_inputs, event.non_indexed_outputs].flatten.map { |output| OpenStruct.new(output) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|