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