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

Returns a new instance of 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.

Parameters:

  • opts (Hash)

    Options to the method.

Returns:



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.

Parameters:

  • name (String)

    The name of the contract whose artifacts to look up.

  • paths (Array<String>) (defaults to: [])

    An additional list of paths to look up; this list, if present, is prepended to the ‘truffle_path`.

Returns:

  • (Hash, nil)

    Returns a hash containing the parsed JSON from the artifacts file; if no file was found, returns ‘nil`.



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.

Returns:

  • (Array<String>)

    Returns the array containing the list of lookup paths.



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.

Parameters:

  • paths (Array<String>, nil)

    The array containing the list of lookup paths; a ‘nil` value is converted to the empty array.



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