evm_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.travis.yml +32 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +22 -0
  10. data/LICENSE.txt +21 -0
  11. data/PREREQUISITES.md +75 -0
  12. data/README.md +665 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +14 -0
  15. data/bin/install_parity +22 -0
  16. data/bin/setup +7 -0
  17. data/contracts/AccountingLib.sol +112 -0
  18. data/contracts/AuditorInterface.sol +4 -0
  19. data/contracts/AuditorRegistry.sol +14 -0
  20. data/contracts/CustodianInterface.sol +27 -0
  21. data/contracts/CustodianRegistry.sol +40 -0
  22. data/contracts/DigixConfiguration.sol +68 -0
  23. data/contracts/Directory.sol +67 -0
  24. data/contracts/DoublyLinked.sol +54 -0
  25. data/contracts/GenericInterface.sol +56 -0
  26. data/contracts/GenericRegistry.sol +15 -0
  27. data/contracts/Gold.sol +105 -0
  28. data/contracts/GoldRegistry.sol +82 -0
  29. data/contracts/GoldTokenLedger.sol +3 -0
  30. data/contracts/Interface.sol +27 -0
  31. data/contracts/Minter.sol +3 -0
  32. data/contracts/Recaster.sol +3 -0
  33. data/contracts/Testing.sol +59 -0
  34. data/contracts/VendorInterface.sol +82 -0
  35. data/contracts/VendorRegistry.sol +39 -0
  36. data/contracts/classic/Digixbot.sol +106 -0
  37. data/contracts/classic/DigixbotConfiguration.sol +62 -0
  38. data/contracts/classic/DigixbotEthereum.sol +86 -0
  39. data/contracts/classic/DigixbotUsers.sol +103 -0
  40. data/contracts/classic/Gold.sol +497 -0
  41. data/contracts/classic/GoldRegistry.sol +503 -0
  42. data/contracts/classic/GoldTokenLedger.sol +560 -0
  43. data/contracts/classic/GoldTokenMinter.sol +607 -0
  44. data/contracts/classic/ParticipantRegistry.sol +94 -0
  45. data/contracts/classic/QueueSample.sol +54 -0
  46. data/evm_client.gemspec +36 -0
  47. data/lib/evm_client.rb +15 -0
  48. data/lib/evm_client/abi.rb +32 -0
  49. data/lib/evm_client/client.rb +146 -0
  50. data/lib/evm_client/contract.rb +341 -0
  51. data/lib/evm_client/contract_event.rb +32 -0
  52. data/lib/evm_client/contract_initializer.rb +54 -0
  53. data/lib/evm_client/decoder.rb +99 -0
  54. data/lib/evm_client/deployment.rb +49 -0
  55. data/lib/evm_client/encoder.rb +118 -0
  56. data/lib/evm_client/event_log.rb +88 -0
  57. data/lib/evm_client/explorer_url_helper.rb +25 -0
  58. data/lib/evm_client/formatter.rb +146 -0
  59. data/lib/evm_client/function.rb +40 -0
  60. data/lib/evm_client/function_input.rb +14 -0
  61. data/lib/evm_client/function_output.rb +14 -0
  62. data/lib/evm_client/http_client.rb +44 -0
  63. data/lib/evm_client/initializer.rb +27 -0
  64. data/lib/evm_client/ipc_client.rb +57 -0
  65. data/lib/evm_client/project_initializer.rb +28 -0
  66. data/lib/evm_client/railtie.rb +12 -0
  67. data/lib/evm_client/singleton.rb +39 -0
  68. data/lib/evm_client/solidity.rb +40 -0
  69. data/lib/evm_client/transaction.rb +41 -0
  70. data/lib/evm_client/version.rb +3 -0
  71. data/lib/tasks/ethereum_contract.rake +27 -0
  72. data/lib/tasks/ethereum_node.rake +52 -0
  73. data/lib/tasks/ethereum_test.rake +32 -0
  74. data/lib/tasks/ethereum_transaction.rake +24 -0
  75. metadata +219 -0
@@ -0,0 +1,94 @@
1
+ contract ParticipantRegistry {
2
+
3
+ enum ParticipantTypes { Admin, Vendor, Custodian, Auditor }
4
+ struct ParticipantDatabase {
5
+ mapping (address => bool) auditors;
6
+ mapping (address => bool) vendors;
7
+ mapping (address => bool) custodians;
8
+ mapping (address => bool) registrars;
9
+ }
10
+
11
+ address owner;
12
+ ParticipantDatabase participantdb;
13
+
14
+ event AddParticipant(address indexed participant, uint indexed participanttype);
15
+ event RemoveParticipant(address indexed participant, uint indexed participanttype);
16
+
17
+ modifier ifowner { if (msg.sender == owner) _ }
18
+ modifier ifregistrar { if ((participantdb.registrars[tx.origin]) || (tx.origin == owner)) _ }
19
+ modifier onlyvendor { if (isVendor(tx.origin) == true) _ }
20
+ modifier onlycustodian { if (isCustodian(tx.origin) == true) _ }
21
+ modifier onlyauditor { if (isAuditor(tx.origin) == true) _ }
22
+
23
+ function ParticipantRegistry() {
24
+ owner = msg.sender;
25
+ }
26
+
27
+ function getOwner() returns (address oa) {
28
+ oa = owner;
29
+ }
30
+
31
+ function setOwner(address nown) ifowner {
32
+ owner = nown;
33
+ }
34
+
35
+ function registerAdmin(address regraddr) ifowner {
36
+ participantdb.registrars[regraddr] = true;
37
+ AddParticipant(regraddr, uint(ParticipantTypes.Admin));
38
+ }
39
+
40
+ function unregisterAdmin(address regraddr) ifowner {
41
+ participantdb.registrars[regraddr] = false;
42
+ RemoveParticipant(regraddr, uint(ParticipantTypes.Admin));
43
+ }
44
+
45
+ function registerVendor(address vendoraddress) ifregistrar {
46
+ participantdb.vendors[vendoraddress] = true;
47
+ AddParticipant(vendoraddress, uint(ParticipantTypes.Vendor));
48
+ }
49
+
50
+ function unregisterVendor(address vendoraddress) ifregistrar {
51
+ participantdb.vendors[vendoraddress] = false;
52
+ RemoveParticipant(vendoraddress, uint(ParticipantTypes.Vendor));
53
+ }
54
+
55
+ function registerCustodian(address custodianaddress) ifregistrar {
56
+ participantdb.custodians[custodianaddress] = true;
57
+ AddParticipant(custodianaddress, uint(ParticipantTypes.Custodian));
58
+ }
59
+
60
+ function unregisterCustodian(address custodianaddress) ifregistrar {
61
+ participantdb.custodians[custodianaddress] = false;
62
+ RemoveParticipant(custodianaddress, uint(ParticipantTypes.Custodian));
63
+ }
64
+
65
+ function registerAuditor(address auditoraddress) ifregistrar {
66
+ participantdb.auditors[auditoraddress] = true;
67
+ AddParticipant(auditoraddress, uint(ParticipantTypes.Auditor));
68
+ }
69
+
70
+ function unregisterAuditor(address auditoraddress) ifregistrar {
71
+ participantdb.auditors[auditoraddress] = false;
72
+ RemoveParticipant(auditoraddress, uint(ParticipantTypes.Auditor));
73
+ }
74
+
75
+ function isVendor(address vendoraddress) returns (bool) {
76
+ return participantdb.vendors[vendoraddress];
77
+ }
78
+
79
+ function isCustodian(address custodianaddress) returns (bool) {
80
+ return participantdb.custodians[custodianaddress];
81
+ }
82
+
83
+ function isAuditor(address auditoraddress) returns (bool) {
84
+ return participantdb.auditors[auditoraddress];
85
+ }
86
+
87
+ function isRegistrar(address regraddr) returns (bool) {
88
+ return participantdb.registrars[regraddr];
89
+ }
90
+ }
91
+
92
+
93
+
94
+ //#include [ParticipantRegistry]
@@ -0,0 +1,54 @@
1
+ ////////////////////////////////////////////////////////////
2
+ // This is an example contract hacked together at a meetup.
3
+ // It is by far not complete and only used to show some
4
+ // features of Solidity.
5
+ ////////////////////////////////////////////////////////////
6
+ contract QueueContract
7
+ {
8
+ struct Queue {
9
+ uint[] data;
10
+ uint front;
11
+ uint back;
12
+ }
13
+ /// @dev the number of elements stored in the queue.
14
+ function length(Queue storage q) constant internal returns (uint) {
15
+ return q.back - q.front;
16
+ }
17
+ /// @dev the number of elements this queue can hold
18
+ function capacity(Queue storage q) constant internal returns (uint) {
19
+ return q.data.length - 1;
20
+ }
21
+ /// @dev push a new element to the back of the queue
22
+ function push(Queue storage q, uint data) internal
23
+ {
24
+ if ((q.back + 1) % q.data.length == q.front)
25
+ return; // throw;
26
+ q.data[q.back] = data;
27
+ q.back = (q.back + 1) % q.data.length;
28
+ }
29
+ /// @dev remove and return the element at the front of the queue
30
+ function pop(Queue storage q) internal returns (uint r)
31
+ {
32
+ if (q.back == q.front)
33
+ return; // throw;
34
+ r = q.data[q.front];
35
+ delete q.data[q.front];
36
+ q.front = (q.front + 1) % q.data.length;
37
+ }
38
+ }
39
+
40
+ contract QueueUserMayBeDeliveryDroneCotnrol is QueueContract {
41
+ Queue requests;
42
+ function QueueUserMayBeDeliveryDroneCotnrol() {
43
+ requests.data.length = 200;
44
+ }
45
+ function addRequest(uint d) {
46
+ push(requests, d);
47
+ }
48
+ function popRequest() returns (uint) {
49
+ return pop(requests);
50
+ }
51
+ function queueLength() returns (uint) {
52
+ return length(requests);
53
+ }
54
+ }
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'evm_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "evm_client"
8
+ spec.version = EvmClient::VERSION
9
+ spec.authors = ["shideneyu"]
10
+ spec.email = ["shideneyu@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby Ethereum client using the JSON-RPC interface}
13
+ spec.description = %q{EvmClient is Ruby Ethereum client using the JSON-RPC interface. Provides interface for sending transactions, creating and interacting with contracts as well as usefull toolkit to work with Ethereum node.}
14
+ spec.homepage = "https://github.com/shideneyu/evm_client"
15
+ spec.license = "MIT"
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib", "bin"]
27
+
28
+ spec.add_development_dependency "bundler", "~> 2.0"
29
+ spec.add_development_dependency "rake", "~> 12.0"
30
+ spec.add_development_dependency "rspec", "~> 3.5"
31
+ spec.add_development_dependency "pry", "~> 0.10"
32
+ spec.add_development_dependency "eth", "~> 0.4"
33
+
34
+ spec.add_dependency "activesupport", ">= 4.0"
35
+ spec.add_dependency "digest-sha3", "~> 1.1"
36
+ end
data/lib/evm_client.rb ADDED
@@ -0,0 +1,15 @@
1
+ require_relative 'evm_client/version'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
+ require 'digest/sha3'
5
+ require 'pry'
6
+
7
+ module EvmClient
8
+ ruby_project_files = Dir[File.join(File.dirname(__FILE__), '**', '*.rb')]
9
+
10
+ ruby_project_files.sort_by!{ |file_name| file_name.downcase }.each do |path|
11
+ require_relative path unless path.match?(/rail/)
12
+ end
13
+
14
+ require_relative 'ethereum/railtie' if defined?(Rails)
15
+ end
@@ -0,0 +1,32 @@
1
+ module EvmClient
2
+ class Abi
3
+
4
+ def self.parse_abi(abi)
5
+ constructor = abi.detect {|x| x["type"] == "constructor"}
6
+ if constructor.present?
7
+ constructor_inputs = constructor["inputs"].map { |input| EvmClient::FunctionInput.new(input) }
8
+ else
9
+ constructor_inputs = []
10
+ end
11
+ functions = abi.select {|x| x["type"] == "function" }.map { |fun| EvmClient::Function.new(fun) }
12
+ events = abi.select {|x| x["type"] == "event" }.map { |evt| EvmClient::ContractEvent.new(evt) }
13
+ [constructor_inputs, functions, events]
14
+ end
15
+
16
+ def self.parse_type(type)
17
+ raise NotImplementedError if type.ends_with?("]")
18
+ match = /(\D+)(\d.*)?/.match(type)
19
+ [match[1], match[2]]
20
+ end
21
+
22
+ def self.parse_array_type(type)
23
+ match = /(.+)\[(\d*)\]\z/.match(type)
24
+ if match
25
+ [true, match[2].present? ? match[2].to_i : nil, match[1]]
26
+ else
27
+ [false, nil, nil]
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,146 @@
1
+ module EvmClient
2
+ class Client
3
+
4
+ DEFAULT_GAS_LIMIT = 4_000_000.freeze
5
+ DEFAULT_GAS_PRICE = 22_000_000_000.freeze
6
+ DEFAULT_BLOCK_NUMBER = 'latest'.freeze
7
+
8
+ # https://github.com/ethereum/wiki/wiki/JSON-RPC
9
+ RPC_COMMANDS = %w(web3_clientVersion web3_sha3 net_version net_peerCount net_listening eth_protocolVersion eth_syncing eth_coinbase eth_mining eth_hashrate eth_gasPrice eth_accounts eth_blockNumber eth_getBalance eth_getStorageAt eth_getTransactionCount eth_getBlockTransactionCountByHash eth_getBlockTransactionCountByNumber eth_getUncleCountByBlockHash eth_getUncleCountByBlockNumber eth_getCode eth_sign eth_sendTransaction eth_sendRawTransaction eth_call eth_estimateGas eth_getBlockByHash eth_getBlockByNumber eth_getTransactionByHash eth_getTransactionByBlockHashAndIndex eth_getTransactionByBlockNumberAndIndex eth_getTransactionReceipt eth_getUncleByBlockHashAndIndex eth_getUncleByBlockNumberAndIndex eth_getCompilers eth_compileLLL eth_compileSolidity eth_compileSerpent eth_newFilter eth_newBlockFilter eth_newPendingTransactionFilter eth_uninstallFilter eth_getFilterChanges eth_getFilterLogs eth_getLogs eth_getWork eth_submitWork eth_submitHashrate db_putString db_getString db_putHex db_getHex shh_post shh_version shh_newIdentity shh_hasIdentity shh_newGroup shh_addToGroup shh_newFilter shh_uninstallFilter shh_getFilterChanges shh_getMessages)
10
+
11
+ # https://github.com/ethereum/go-ethereum/wiki/Management-APIs
12
+ RPC_MANAGEMENT_COMMANDS = %w(admin_addPeer admin_datadir admin_nodeInfo admin_peers admin_setSolc admin_startRPC admin_startWS admin_stopRPC admin_stopWS debug_backtraceAt debug_blockProfile debug_cpuProfile debug_dumpBlock debug_gcStats debug_getBlockRlp debug_goTrace debug_memStats debug_seedHash debug_setHead debug_setBlockProfileRate debug_stacks debug_startCPUProfile debug_startGoTrace debug_stopCPUProfile debug_stopGoTrace debug_traceBlock debug_traceBlockByNumber debug_traceBlockByHash debug_traceBlockFromFile debug_traceTransaction debug_verbosity debug_vmodule debug_writeBlockProfile debug_writeMemProfile miner_hashrate miner_makeDAG miner_setExtra miner_setGasPrice miner_start miner_startAutoDAG miner_stop miner_stopAutoDAG personal_importRawKey personal_listAccounts personal_lockAccount personal_newAccount personal_unlockAccount personal_sendTransaction txpool_content txpool_inspect txpool_status)
13
+
14
+ attr_accessor :command, :id, :log, :logger, :default_account, :gas_price, :gas_limit, :block_number
15
+
16
+ def initialize(log = false)
17
+ @id = 0
18
+ @log = log
19
+ @batch = nil
20
+ @formatter = ::EvmClient::Formatter.new
21
+ @gas_price = DEFAULT_GAS_PRICE
22
+ @gas_limit = DEFAULT_GAS_LIMIT
23
+ @block_number = DEFAULT_BLOCK_NUMBER
24
+
25
+ if @log == true
26
+ @logger = Logger.new("/tmp/ethereum_ruby_http.log")
27
+ end
28
+ end
29
+
30
+ def self.create(host_or_ipcpath, log = false)
31
+ return IpcClient.new(host_or_ipcpath, log) if host_or_ipcpath.end_with? '.ipc'
32
+ return HttpClient.new(host_or_ipcpath, log) if host_or_ipcpath.start_with? 'http'
33
+ raise ArgumentError.new('Unable to detect client type')
34
+ end
35
+
36
+ def batch
37
+ @batch = []
38
+
39
+ yield
40
+ result = send_batch(@batch)
41
+
42
+ @batch = nil
43
+ reset_id
44
+
45
+ return result
46
+ end
47
+
48
+ def get_id
49
+ @id += 1
50
+ return @id
51
+ end
52
+
53
+ def reset_id
54
+ @id = 0
55
+ end
56
+
57
+ def default_account
58
+ @default_account ||= eth_accounts["result"][0]
59
+ end
60
+
61
+ def int_to_hex(p)
62
+ p.is_a?(Integer) ? "0x#{p.to_s(16)}" : p
63
+ end
64
+
65
+ def encode_params(params)
66
+ params.map(&method(:int_to_hex))
67
+ end
68
+
69
+ def get_balance(address)
70
+ eth_get_balance(address)["result"].to_i(16)
71
+ end
72
+
73
+ def get_chain
74
+ @net_version ||= net_version["result"].to_i
75
+ end
76
+
77
+ def get_nonce(address)
78
+ eth_get_transaction_count(address, "pending")["result"].to_i(16)
79
+ end
80
+
81
+
82
+ def transfer_to(address, amount)
83
+ eth_send_transaction({to: address, value: int_to_hex(amount)})
84
+ end
85
+
86
+ def transfer_to_and_wait(address, amount)
87
+ wait_for(transfer_to(address, amount)["result"])
88
+ end
89
+
90
+
91
+ def transfer(key, address, amount)
92
+ Eth.configure { |c| c.chain_id = net_version["result"].to_i }
93
+ args = {
94
+ from: key.address,
95
+ to: address,
96
+ value: amount,
97
+ data: "",
98
+ nonce: get_nonce(key.address),
99
+ gas_limit: gas_limit,
100
+ gas_price: gas_price
101
+ }
102
+ tx = Eth::Tx.new(args)
103
+ tx.sign key
104
+ eth_send_raw_transaction(tx.hex)["result"]
105
+ end
106
+
107
+ def transfer_and_wait(key, address, amount)
108
+ return wait_for(transfer(key, address, amount))
109
+ end
110
+
111
+ def wait_for(tx)
112
+ transaction = EvmClient::Transaction.new(tx, self, "", [])
113
+ transaction.wait_for_miner
114
+ return transaction
115
+ end
116
+
117
+ def send_command(command,args)
118
+ if ["eth_getBalance", "eth_call"].include?(command)
119
+ args << block_number
120
+ end
121
+
122
+ payload = {jsonrpc: "2.0", method: command, params: encode_params(args), id: get_id}
123
+ @logger.info("Sending #{payload.to_json}") if @log
124
+ if @batch
125
+ @batch << payload
126
+ return true
127
+ else
128
+ output = JSON.parse(send_single(payload.to_json))
129
+ @logger.info("Received #{output.to_json}") if @log
130
+ reset_id
131
+ raise IOError, output["error"]["message"] if output["error"]
132
+ return output
133
+ end
134
+ end
135
+
136
+ (RPC_COMMANDS + RPC_MANAGEMENT_COMMANDS).each do |rpc_command|
137
+ method_name = rpc_command.underscore
138
+ define_method method_name do |*args|
139
+ send_command(rpc_command, args)
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
@@ -0,0 +1,341 @@
1
+ require 'forwardable'
2
+ module EvmClient
3
+ class Contract
4
+
5
+ attr_reader :address
6
+ attr_accessor :key
7
+ attr_accessor :gas_limit, :gas_price, :nonce
8
+ attr_accessor :code, :name, :abi, :class_object, :sender, :deployment, :client
9
+ attr_accessor :events, :functions, :constructor_inputs
10
+ attr_accessor :call_raw_proxy, :call_proxy, :transact_proxy, :transact_and_wait_proxy
11
+ attr_accessor :new_filter_proxy, :get_filter_logs_proxy, :get_filter_change_proxy
12
+
13
+ def initialize(name, code, abi, client = EvmClient::Singleton.instance)
14
+ @name = name
15
+ @code = code
16
+ @abi = abi
17
+ @constructor_inputs, @functions, @events = EvmClient::Abi.parse_abi(abi)
18
+ @formatter = EvmClient::Formatter.new
19
+ @client = client
20
+ @sender = client.default_account
21
+ @encoder = Encoder.new
22
+ @decoder = Decoder.new
23
+ @gas_limit = @client.gas_limit
24
+ @gas_price = @client.gas_price
25
+ end
26
+
27
+ # Creates a contract wrapper.
28
+ # This method attempts to instantiate a contract object from a Solidity source file, from a Truffle
29
+ # artifacts file, or from explicit API and bytecode.
30
+ # - If *:file* is present, the method compiles it and looks up the contract factory at *:contract_index*
31
+ # (0 if not provided). *:abi* and *:code* are ignored.
32
+ # - If *:truffle* is present, the method looks up the Truffle artifacts data for *:name* and uses
33
+ # those data to build a contract instance.
34
+ # - Otherwise, the method uses *:name*, *:code*, and *:abi* to build the contract instance.
35
+ #
36
+ # @param opts [Hash] Options to the method.
37
+ # @option opts [String] :file Path to the Solidity source that contains the contract code.
38
+ # @option opts [EvmClient::Singleton] :client The client to use.
39
+ # @option opts [String] :code The hex representation of the contract's bytecode.
40
+ # @option opts [Array,String] :abi The contract's ABI; a string is assumed to contain a JSON representation
41
+ # of the ABI.
42
+ # @option opts [String] :address The contract's address; if not present and +:truffle+ is present,
43
+ # the method attempts to determine the address from the artifacts' +networks+ key and the client's
44
+ # network id.
45
+ # @option opts [String] :name The contract name.
46
+ # @option opts [Integer] :contract_index The index of the contract data in the compiled file.
47
+ # @option opts [Hash] :truffle If this parameter is present, the method uses Truffle information to
48
+ # generate the contract wrapper.
49
+ # - *:paths* An array of strings containing the list of paths where to look up Truffle artifacts files.
50
+ # See also {#find_truffle_artifacts}.
51
+ #
52
+ # @return [EvmClient::Contract] Returns a contract wrapper.
53
+
54
+ def self.create(file: nil, client: EvmClient::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil)
55
+ contract = nil
56
+ if file.present?
57
+ contracts = EvmClient::Initializer.new(file, client).build_all
58
+ raise "No contracts compiled" if contracts.empty?
59
+ if contract_index
60
+ contract = contracts[contract_index].class_object.new
61
+ else
62
+ contract = contracts.first.class_object.new
63
+ end
64
+ else
65
+ if truffle.present? && truffle.is_a?(Hash)
66
+ artifacts = find_truffle_artifacts(name, (truffle[:paths].is_a?(Array)) ? truffle[:paths] : [])
67
+ if artifacts
68
+ abi = artifacts['abi']
69
+ # The truffle artifacts store bytecodes with a 0x tag, which we need to remove
70
+ # this may need to be 'deployedBytecode'
71
+ code_key = artifacts['bytecode'].present? ? 'bytecode' : 'unlinked_binary'
72
+ code = (artifacts[code_key].start_with?('0x')) ? artifacts[code_key][2, artifacts[code_key].length] : artifacts[code_key]
73
+ unless address
74
+ address = if client
75
+ network_id = client.net_version['result']
76
+ (artifacts['networks'][network_id]) ? artifacts['networks'][network_id]['address'] : nil
77
+ else
78
+ nil
79
+ end
80
+ end
81
+ else
82
+ abi = nil
83
+ code = nil
84
+ end
85
+ else
86
+ abi = abi.is_a?(String) ? JSON.parse(abi) : abi.map(&:deep_stringify_keys)
87
+ end
88
+ contract = EvmClient::Contract.new(name, code, abi, client)
89
+ contract.build
90
+ contract = contract.class_object.new
91
+ end
92
+ contract.address = address
93
+ contract
94
+ end
95
+
96
+ def address=(addr)
97
+ @address = addr
98
+ @events.each do |event|
99
+ event.set_address(addr)
100
+ event.set_client(@client)
101
+ end
102
+ end
103
+
104
+ def deploy_payload(params)
105
+ if @constructor_inputs.present?
106
+ raise ArgumentError, "Wrong number of arguments in a constructor" and return if params.length != @constructor_inputs.length
107
+ end
108
+ deploy_arguments = @encoder.encode_arguments(@constructor_inputs, params)
109
+ "0x" + @code + deploy_arguments
110
+ end
111
+
112
+ def deploy_args(params)
113
+ add_gas_options_args({from: sender, data: deploy_payload(params)})
114
+ end
115
+
116
+ def send_transaction(tx_args)
117
+ @client.eth_send_transaction(tx_args)["result"]
118
+ end
119
+
120
+ def send_raw_transaction(payload, to = nil)
121
+ Eth.configure { |c| c.chain_id = @client.net_version["result"].to_i }
122
+ @nonce ||= @client.get_nonce(key.address)
123
+ args = {
124
+ from: key.address,
125
+ value: 0,
126
+ data: payload,
127
+ nonce: @nonce,
128
+ gas_limit: gas_limit,
129
+ gas_price: gas_price
130
+ }
131
+ args[:to] = to if to
132
+ tx = Eth::Tx.new(args)
133
+ tx.sign key
134
+ @client.eth_send_raw_transaction(tx.hex)["result"]
135
+ end
136
+
137
+ def deploy(*params)
138
+ if key
139
+ tx = send_raw_transaction(deploy_payload(params))
140
+ else
141
+ tx = send_transaction(deploy_args(params))
142
+ end
143
+ tx_failed = tx.nil? || tx == "0x0000000000000000000000000000000000000000000000000000000000000000"
144
+ raise IOError, "Failed to deploy, did you unlock #{sender} account? Transaction hash: #{tx}" if tx_failed
145
+ @deployment = EvmClient::Deployment.new(tx, @client)
146
+ end
147
+
148
+ def deploy_and_wait(*params, **args, &block)
149
+ deploy(*params)
150
+ @deployment.wait_for_deployment(**args, &block)
151
+ self.events.each do |event|
152
+ event.set_address(@address)
153
+ event.set_client(@client)
154
+ end
155
+ @address = @deployment.contract_address
156
+ end
157
+
158
+ def estimate(*params)
159
+ result = @client.eth_estimate_gas(deploy_args(params))
160
+ @decoder.decode_int(result["result"])
161
+ end
162
+
163
+ def call_payload(fun, args)
164
+ "0x" + fun.minified_signature + (@encoder.encode_arguments(fun.inputs, args))
165
+ end
166
+
167
+ def call_args(fun, args)
168
+ options = {to: @address, from: @sender, data: call_payload(fun, args)}
169
+
170
+ fun.constant ? options : add_gas_options_args(options)
171
+ end
172
+
173
+ def call_raw(fun, *args)
174
+ raw_result = @client.eth_call(call_args(fun, args))["result"]
175
+ output = @decoder.decode_arguments(fun.outputs, raw_result)
176
+ return {data: call_payload(fun, args), raw: raw_result, formatted: output}
177
+ end
178
+
179
+ def call(fun, *args)
180
+ output = call_raw(fun, *args)[:formatted]
181
+ if output.length == 1
182
+ return output[0]
183
+ else
184
+ return output
185
+ end
186
+ end
187
+
188
+ def transact(fun, *args)
189
+ if key
190
+ tx = send_raw_transaction(call_payload(fun, args), address)
191
+ else
192
+ tx = send_transaction(call_args(fun, args))
193
+ end
194
+ return EvmClient::Transaction.new(tx, @client, call_payload(fun, args), args)
195
+ end
196
+
197
+ def transact_and_wait(fun, *args)
198
+ tx = transact(fun, *args)
199
+ tx.wait_for_miner
200
+ return tx
201
+ end
202
+
203
+ def create_filter(evt, **params)
204
+ params[:to_block] ||= "latest"
205
+ params[:from_block] ||= "0x0"
206
+ params[:address] ||= @address
207
+ params[:topics] = @encoder.ensure_prefix(evt.signature)
208
+ payload = {topics: [params[:topics]], fromBlock: params[:from_block], toBlock: params[:to_block], address: @encoder.ensure_prefix(params[:address])}
209
+ filter_id = @client.eth_new_filter(payload)
210
+ return @decoder.decode_int(filter_id["result"])
211
+ end
212
+
213
+ def parse_filter_data(evt, logs)
214
+ formatter = EvmClient::Formatter.new
215
+ collection = []
216
+ logs["result"].each do |result|
217
+ inputs = evt.input_types
218
+ outputs = inputs.zip(result["topics"][1..-1])
219
+ data = {blockNumber: result["blockNumber"].hex, transactionHash: result["transactionHash"], blockHash: result["blockHash"], transactionIndex: result["transactionIndex"].hex, topics: []}
220
+ outputs.each do |output|
221
+ data[:topics] << formatter.from_payload(output)
222
+ end
223
+ collection << data
224
+ end
225
+ return collection
226
+ end
227
+
228
+ def get_filter_logs(evt, filter_id)
229
+ parse_filter_data evt, @client.eth_get_filter_logs(filter_id)
230
+ end
231
+
232
+ def get_filter_changes(evt, filter_id)
233
+ parse_filter_data evt, @client.eth_get_filter_changes(filter_id)
234
+ end
235
+
236
+ def function_name(fun)
237
+ count = functions.select {|x| x.name == fun.name }.count
238
+ name = (count == 1) ? "#{fun.name.underscore}" : "#{fun.name.underscore}__#{fun.inputs.collect {|x| x.type}.join("__")}"
239
+ name.to_sym
240
+ end
241
+
242
+ def build
243
+ class_name = @name.camelize
244
+ parent = self
245
+ create_function_proxies
246
+ create_event_proxies
247
+ class_methods = Class.new do
248
+ extend Forwardable
249
+ def_delegators :parent, :deploy_payload, :deploy_args, :call_payload, :call_args, :functions
250
+ def_delegators :parent, :signed_deploy, :key, :key=
251
+ def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
252
+ def_delegators :parent, :abi, :deployment, :events, :name
253
+ def_delegators :parent, :estimate, :deploy, :deploy_and_wait
254
+ def_delegators :parent, :address, :address=, :sender, :sender=
255
+ def_delegator :parent, :call_raw_proxy, :call_raw
256
+ def_delegator :parent, :call_proxy, :call
257
+ def_delegator :parent, :transact_proxy, :transact
258
+ def_delegator :parent, :transact_and_wait_proxy, :transact_and_wait
259
+ def_delegator :parent, :new_filter_proxy, :new_filter
260
+ def_delegator :parent, :get_filter_logs_proxy, :get_filter_logs
261
+ def_delegator :parent, :get_filter_change_proxy, :get_filter_changes
262
+ define_method :parent do
263
+ parent
264
+ end
265
+ end
266
+ EvmClient::Contract.send(:remove_const, class_name) if EvmClient::Contract.const_defined?(class_name, false)
267
+ EvmClient::Contract.const_set(class_name, class_methods)
268
+ @class_object = class_methods
269
+ end
270
+
271
+ # Get the list of paths where to look up Truffle artifacts files.
272
+ #
273
+ # @return [Array<String>] Returns the array containing the list of lookup paths.
274
+
275
+ def self.truffle_paths()
276
+ @truffle_paths = [] unless @truffle_paths
277
+ @truffle_paths
278
+ end
279
+
280
+ # Set the list of paths where to look up Truffle artifacts files.
281
+ #
282
+ # @param paths [Array<String>, nil] The array containing the list of lookup paths; a `nil` value is
283
+ # converted to the empty array.
284
+
285
+ def self.truffle_paths=(paths)
286
+ @truffle_paths = (paths.is_a?(Array)) ? paths : []
287
+ end
288
+
289
+ # Looks up and loads a Truffle artifacts file.
290
+ # This method iterates over the `truffle_path` elements, looking for an artifact file in the
291
+ # `build/contracts` subdirectory.
292
+ #
293
+ # @param name [String] The name of the contract whose artifacts to look up.
294
+ # @param paths [Array<String>] An additional list of paths to look up; this list, if present, is
295
+ # prepended to the `truffle_path`.
296
+ #
297
+ # @return [Hash,nil] Returns a hash containing the parsed JSON from the artifacts file; if no file
298
+ # was found, returns `nil`.
299
+
300
+ def self.find_truffle_artifacts(name, paths = [])
301
+ subpath = File.join('build', 'contracts', "#{name}.json")
302
+
303
+ found = paths.concat(truffle_paths).find { |p| File.file?(File.join(p, subpath)) }
304
+ if (found)
305
+ JSON.parse(IO.read(File.join(found, subpath)))
306
+ else
307
+ nil
308
+ end
309
+ end
310
+
311
+ private
312
+ def add_gas_options_args(args)
313
+ args[:gas] = @client.int_to_hex(gas_limit) if gas_limit.present?
314
+ args[:gasPrice] = @client.int_to_hex(gas_price) if gas_price.present?
315
+ args
316
+ end
317
+
318
+ def create_function_proxies
319
+ parent = self
320
+ call_raw_proxy, call_proxy, transact_proxy, transact_and_wait_proxy = Class.new, Class.new, Class.new, Class.new
321
+ @functions.each do |fun|
322
+ call_raw_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call_raw(fun, *args) }
323
+ call_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call(fun, *args) }
324
+ transact_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact(fun, *args) }
325
+ transact_and_wait_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact_and_wait(fun, *args) }
326
+ end
327
+ @call_raw_proxy, @call_proxy, @transact_proxy, @transact_and_wait_proxy = call_raw_proxy.allocate, call_proxy.allocate, transact_proxy.allocate, transact_and_wait_proxy.allocate
328
+ end
329
+
330
+ def create_event_proxies
331
+ parent = self
332
+ new_filter_proxy, get_filter_logs_proxy, get_filter_change_proxy = Class.new, Class.new, Class.new
333
+ events.each do |evt|
334
+ new_filter_proxy.send(:define_method, evt.name.underscore) { |*args| parent.create_filter(evt, *args) }
335
+ get_filter_logs_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_logs(evt, *args) }
336
+ get_filter_change_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_changes(evt, *args) }
337
+ end
338
+ @new_filter_proxy, @get_filter_logs_proxy, @get_filter_change_proxy = new_filter_proxy.allocate, get_filter_logs_proxy.allocate, get_filter_change_proxy.allocate
339
+ end
340
+ end
341
+ end