Class: EvmClient::Contract

Inherits:
Object
  • Object
show all
Defined in:
lib/evm_client/contract.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, code, abi, client = EvmClient::Singleton.instance) ⇒ Contract



13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/evm_client/contract.rb', line 13

def initialize(name, code, abi, client = EvmClient::Singleton.instance)
  @name = name
  @code = code
  @abi = abi
  @constructor_inputs, @functions, @events = EvmClient::Abi.parse_abi(abi)
  @formatter = EvmClient::Formatter.new
  @client = client
  @sender = client.
  @encoder = Encoder.new
  @decoder = Decoder.new
  @gas_limit = @client.gas_limit
  @gas_price = @client.gas_price
end

Instance Attribute Details

#abiObject

Returns the value of attribute abi.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def abi
  @abi
end

#addressObject

Returns the value of attribute address.



5
6
7
# File 'lib/evm_client/contract.rb', line 5

def address
  @address
end

#call_proxyObject

Returns the value of attribute call_proxy.



10
11
12
# File 'lib/evm_client/contract.rb', line 10

def call_proxy
  @call_proxy
end

#call_raw_proxyObject

Returns the value of attribute call_raw_proxy.



10
11
12
# File 'lib/evm_client/contract.rb', line 10

def call_raw_proxy
  @call_raw_proxy
end

#class_objectObject

Returns the value of attribute class_object.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def class_object
  @class_object
end

#clientObject

Returns the value of attribute client.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def client
  @client
end

#codeObject

Returns the value of attribute code.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def code
  @code
end

#constructor_inputsObject

Returns the value of attribute constructor_inputs.



9
10
11
# File 'lib/evm_client/contract.rb', line 9

def constructor_inputs
  @constructor_inputs
end

#deploymentObject

Returns the value of attribute deployment.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def deployment
  @deployment
end

#eventsObject

Returns the value of attribute events.



9
10
11
# File 'lib/evm_client/contract.rb', line 9

def events
  @events
end

#functionsObject

Returns the value of attribute functions.



9
10
11
# File 'lib/evm_client/contract.rb', line 9

def functions
  @functions
end

#gas_limitObject

Returns the value of attribute gas_limit.



7
8
9
# File 'lib/evm_client/contract.rb', line 7

def gas_limit
  @gas_limit
end

#gas_priceObject

Returns the value of attribute gas_price.



7
8
9
# File 'lib/evm_client/contract.rb', line 7

def gas_price
  @gas_price
end

#get_filter_change_proxyObject

Returns the value of attribute get_filter_change_proxy.



11
12
13
# File 'lib/evm_client/contract.rb', line 11

def get_filter_change_proxy
  @get_filter_change_proxy
end

#get_filter_logs_proxyObject

Returns the value of attribute get_filter_logs_proxy.



11
12
13
# File 'lib/evm_client/contract.rb', line 11

def get_filter_logs_proxy
  @get_filter_logs_proxy
end

#keyObject

Returns the value of attribute key.



6
7
8
# File 'lib/evm_client/contract.rb', line 6

def key
  @key
end

#nameObject

Returns the value of attribute name.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def name
  @name
end

#new_filter_proxyObject

Returns the value of attribute new_filter_proxy.



11
12
13
# File 'lib/evm_client/contract.rb', line 11

def new_filter_proxy
  @new_filter_proxy
end

#nonceObject

Returns the value of attribute nonce.



7
8
9
# File 'lib/evm_client/contract.rb', line 7

def nonce
  @nonce
end

#senderObject

Returns the value of attribute sender.



8
9
10
# File 'lib/evm_client/contract.rb', line 8

def sender
  @sender
end

#transact_and_wait_proxyObject

Returns the value of attribute transact_and_wait_proxy.



10
11
12
# File 'lib/evm_client/contract.rb', line 10

def transact_and_wait_proxy
  @transact_and_wait_proxy
end

#transact_proxyObject

Returns the value of attribute transact_proxy.



10
11
12
# File 'lib/evm_client/contract.rb', line 10

def transact_proxy
  @transact_proxy
end

Class Method Details

.create(file: nil, client: EvmClient::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil) ⇒ EvmClient::Contract

Creates a contract wrapper. This method attempts to instantiate a contract object from a Solidity source file, from a Truffle artifacts file, or from explicit API and bytecode.

  • If :file is present, the method compiles it and looks up the contract factory at :contract_index (0 if not provided). :abi and :code are ignored.

  • If :truffle is present, the method looks up the Truffle artifacts data for :name and uses those data to build a contract instance.

  • Otherwise, the method uses :name, :code, and :abi to build the contract instance.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/evm_client/contract.rb', line 54

def self.create(file: nil, client: EvmClient::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil)
  contract = nil
  if file.present?
    contracts = EvmClient::Initializer.new(file, client).build_all
    raise "No contracts compiled" if contracts.empty?
    if contract_index
      contract = contracts[contract_index].class_object.new
    else
      contract = contracts.first.class_object.new
    end
  else
    if truffle.present? && truffle.is_a?(Hash)
      artifacts = find_truffle_artifacts(name, (truffle[:paths].is_a?(Array)) ? truffle[:paths] : [])
      if artifacts
        abi = artifacts['abi']
        # The truffle artifacts store bytecodes with a 0x tag, which we need to remove
        # this may need to be 'deployedBytecode'
        code_key = artifacts['bytecode'].present? ? 'bytecode' : 'unlinked_binary'
        code = (artifacts[code_key].start_with?('0x')) ? artifacts[code_key][2, artifacts[code_key].length] : artifacts[code_key]
        unless address
          address = if client
                      network_id = client.net_version['result']
                      (artifacts['networks'][network_id]) ? artifacts['networks'][network_id]['address'] : nil
                    else
                      nil
                    end
        end
      else
        abi = nil
        code = nil
      end
    else
      abi = abi.is_a?(String) ? JSON.parse(abi) : abi.map(&:deep_stringify_keys)
    end
    contract = EvmClient::Contract.new(name, code, abi, client)
    contract.build
    contract = contract.class_object.new
  end
  contract.address = address
  contract
end

.find_truffle_artifacts(name, paths = []) ⇒ Hash?

Looks up and loads a Truffle artifacts file. This method iterates over the ‘truffle_path` elements, looking for an artifact file in the `build/contracts` subdirectory.



300
301
302
303
304
305
306
307
308
309
# File 'lib/evm_client/contract.rb', line 300

def self.find_truffle_artifacts(name, paths = [])
  subpath = File.join('build', 'contracts', "#{name}.json")

  found = paths.concat(truffle_paths).find { |p| File.file?(File.join(p, subpath)) }
  if (found)
    JSON.parse(IO.read(File.join(found, subpath)))
  else
    nil
  end
end

.truffle_pathsArray<String>

Get the list of paths where to look up Truffle artifacts files.



275
276
277
278
# File 'lib/evm_client/contract.rb', line 275

def self.truffle_paths()
  @truffle_paths = [] unless @truffle_paths
  @truffle_paths
end

.truffle_paths=(paths) ⇒ Object

Set the list of paths where to look up Truffle artifacts files.



285
286
287
# File 'lib/evm_client/contract.rb', line 285

def self.truffle_paths=(paths)
  @truffle_paths = (paths.is_a?(Array)) ? paths : []
end

Instance Method Details

#buildObject



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/evm_client/contract.rb', line 242

def build
  class_name = @name.camelize
  parent = self
  create_function_proxies
  create_event_proxies
  class_methods = Class.new do
    extend Forwardable
    def_delegators :parent, :deploy_payload, :deploy_args, :call_payload, :call_args, :functions
    def_delegators :parent, :signed_deploy, :key, :key=
    def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
    def_delegators :parent, :abi, :deployment, :events, :name
    def_delegators :parent, :estimate, :deploy, :deploy_and_wait
    def_delegators :parent, :address, :address=, :sender, :sender=
    def_delegator :parent, :call_raw_proxy, :call_raw
    def_delegator :parent, :call_proxy, :call
    def_delegator :parent, :transact_proxy, :transact
    def_delegator :parent, :transact_and_wait_proxy, :transact_and_wait
    def_delegator :parent, :new_filter_proxy, :new_filter
    def_delegator :parent, :get_filter_logs_proxy, :get_filter_logs
    def_delegator :parent, :get_filter_change_proxy, :get_filter_changes
    define_method :parent do
      parent
    end
  end
  EvmClient::Contract.send(:remove_const, class_name) if EvmClient::Contract.const_defined?(class_name, false)
  EvmClient::Contract.const_set(class_name, class_methods)
  @class_object = class_methods
end

#call(fun, *args) ⇒ Object



179
180
181
182
183
184
185
186
# File 'lib/evm_client/contract.rb', line 179

def call(fun, *args)
  output = call_raw(fun, *args)[:formatted]
  if output.length == 1
    return output[0]
  else
    return output
  end
end

#call_args(fun, args) ⇒ Object



167
168
169
170
171
# File 'lib/evm_client/contract.rb', line 167

def call_args(fun, args)
  options = {to: @address, from: @sender, data: call_payload(fun, args)}

  fun.constant ? options : add_gas_options_args(options)
end

#call_payload(fun, args) ⇒ Object



163
164
165
# File 'lib/evm_client/contract.rb', line 163

def call_payload(fun, args)
  "0x" + fun.minified_signature + (@encoder.encode_arguments(fun.inputs, args))
end

#call_raw(fun, *args) ⇒ Object



173
174
175
176
177
# File 'lib/evm_client/contract.rb', line 173

def call_raw(fun, *args)
  raw_result = @client.eth_call(call_args(fun, args))["result"]
  output = @decoder.decode_arguments(fun.outputs, raw_result)
  return {data: call_payload(fun, args), raw: raw_result, formatted: output}
end

#create_filter(evt, **params) ⇒ Object



203
204
205
206
207
208
209
210
211
# File 'lib/evm_client/contract.rb', line 203

def create_filter(evt, **params)
  params[:to_block] ||= "latest"
  params[:from_block] ||= "0x0"
  params[:address] ||= @address
  params[:topics] = @encoder.ensure_prefix(evt.signature)
  payload = {topics: [params[:topics]], fromBlock: params[:from_block], toBlock: params[:to_block], address: @encoder.ensure_prefix(params[:address])}
  filter_id = @client.eth_new_filter(payload)
  return @decoder.decode_int(filter_id["result"])
end

#deploy(*params) ⇒ Object

Raises:

  • (IOError)


137
138
139
140
141
142
143
144
145
146
# File 'lib/evm_client/contract.rb', line 137

def deploy(*params)
  if key
    tx = send_raw_transaction(deploy_payload(params))
  else
    tx = send_transaction(deploy_args(params))
  end
  tx_failed = tx.nil? || tx == "0x0000000000000000000000000000000000000000000000000000000000000000"
  raise IOError, "Failed to deploy, did you unlock #{sender} account? Transaction hash: #{tx}" if tx_failed
  @deployment = EvmClient::Deployment.new(tx, @client)
end

#deploy_and_wait(*params, **args, &block) ⇒ Object



148
149
150
151
152
153
154
155
156
# File 'lib/evm_client/contract.rb', line 148

def deploy_and_wait(*params, **args, &block)
  deploy(*params)
  @deployment.wait_for_deployment(**args, &block)
  self.events.each do |event|
    event.set_address(@address)
    event.set_client(@client)
  end
  @address = @deployment.contract_address
end

#deploy_args(params) ⇒ Object



112
113
114
# File 'lib/evm_client/contract.rb', line 112

def deploy_args(params)
  add_gas_options_args({from: sender, data: deploy_payload(params)})
end

#deploy_payload(params) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/evm_client/contract.rb', line 104

def deploy_payload(params)
  if @constructor_inputs.present?
    raise ArgumentError, "Wrong number of arguments in a constructor" and return if params.length != @constructor_inputs.length
  end
  deploy_arguments = @encoder.encode_arguments(@constructor_inputs, params)
  "0x" + @code + deploy_arguments
end

#estimate(*params) ⇒ Object



158
159
160
161
# File 'lib/evm_client/contract.rb', line 158

def estimate(*params)
  result = @client.eth_estimate_gas(deploy_args(params))
  @decoder.decode_int(result["result"])
end

#function_name(fun) ⇒ Object



236
237
238
239
240
# File 'lib/evm_client/contract.rb', line 236

def function_name(fun)
  count = functions.select {|x| x.name == fun.name }.count
  name = (count == 1) ? "#{fun.name.underscore}" : "#{fun.name.underscore}__#{fun.inputs.collect {|x| x.type}.join("__")}"
  name.to_sym
end

#get_filter_changes(evt, filter_id) ⇒ Object



232
233
234
# File 'lib/evm_client/contract.rb', line 232

def get_filter_changes(evt, filter_id)
  parse_filter_data evt, @client.eth_get_filter_changes(filter_id)
end

#get_filter_logs(evt, filter_id) ⇒ Object



228
229
230
# File 'lib/evm_client/contract.rb', line 228

def get_filter_logs(evt, filter_id)
  parse_filter_data evt, @client.eth_get_filter_logs(filter_id)
end

#parse_filter_data(evt, logs) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/evm_client/contract.rb', line 213

def parse_filter_data(evt, logs)
  formatter = EvmClient::Formatter.new
  collection = []
  logs["result"].each do |result|
    inputs = evt.input_types
    outputs = inputs.zip(result["topics"][1..-1])
    data = {blockNumber: result["blockNumber"].hex, transactionHash: result["transactionHash"], blockHash: result["blockHash"], transactionIndex: result["transactionIndex"].hex, topics: []}
    outputs.each do |output|
      data[:topics] << formatter.from_payload(output)
    end
    collection << data
  end
  return collection
end

#send_raw_transaction(payload, to = nil) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/evm_client/contract.rb', line 120

def send_raw_transaction(payload, to = nil)
  Eth.configure { |c| c.chain_id = @client.net_version["result"].to_i }
  @nonce ||= @client.get_nonce(key.address)
  args = {
    from: key.address,
    value: 0,
    data: payload,
    nonce: @nonce,
    gas_limit: gas_limit,
    gas_price: gas_price
  }
  args[:to] = to if to
  tx = Eth::Tx.new(args)
  tx.sign key
  @client.eth_send_raw_transaction(tx.hex)["result"]
end

#send_transaction(tx_args) ⇒ Object



116
117
118
# File 'lib/evm_client/contract.rb', line 116

def send_transaction(tx_args)
    @client.eth_send_transaction(tx_args)["result"]
end

#transact(fun, *args) ⇒ Object



188
189
190
191
192
193
194
195
# File 'lib/evm_client/contract.rb', line 188

def transact(fun, *args)
  if key
    tx = send_raw_transaction(call_payload(fun, args), address)
  else
    tx = send_transaction(call_args(fun, args))
  end
  return EvmClient::Transaction.new(tx, @client, call_payload(fun, args), args)
end

#transact_and_wait(fun, *args) ⇒ Object



197
198
199
200
201
# File 'lib/evm_client/contract.rb', line 197

def transact_and_wait(fun, *args)
  tx = transact(fun, *args)
  tx.wait_for_miner
  return tx
end