class EvmClient::Contract

Attributes

abi[RW]
address[R]
call_proxy[RW]
call_raw_proxy[RW]
class_object[RW]
client[RW]
code[RW]
constructor_inputs[RW]
deployment[RW]
events[RW]
functions[RW]
gas_limit[RW]
gas_price[RW]
get_filter_change_proxy[RW]
get_filter_logs_proxy[RW]
key[RW]
name[RW]
new_filter_proxy[RW]
nonce[RW]
sender[RW]
transact_and_wait_proxy[RW]
transact_proxy[RW]

Public Class Methods

create(file: nil, client: EvmClient::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil) click to toggle source

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.

@param opts [Hash] Options to the method. @option opts [String] :file Path to the Solidity source that contains the contract code. @option opts [EvmClient::Singleton] :client The client to use. @option opts [String] :code The hex representation of the contract's bytecode. @option opts [Array,String] :abi The contract's ABI; a string is assumed to contain a JSON representation

of the ABI.

@option opts [String] :address The contract's address; if not present and :truffle is present,

the method attempts to determine the address from the artifacts' +networks+ key and the client's
network id.

@option opts [String] :name The contract name. @option opts [Integer] :contract_index The index of the contract data in the compiled file. @option opts [Hash] :truffle If this parameter is present, the method uses Truffle information to

generate the contract wrapper.
- *:paths* An array of strings containing the list of paths where to look up Truffle artifacts files.
  See also {#find_truffle_artifacts}.

@return [EvmClient::Contract] Returns a contract wrapper.

# 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 = []) click to toggle source

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.

@param name [String] The name of the contract whose artifacts to look up. @param paths [Array<String>] An additional list of paths to look up; this list, if present, is

prepended to the `truffle_path`.

@return [Hash,nil] Returns a hash containing the parsed JSON from the artifacts file; if no file

was found, returns `nil`.
# 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
new(name, code, abi, client = EvmClient::Singleton.instance) click to toggle source
# 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.default_account
  @encoder = Encoder.new
  @decoder = Decoder.new
  @gas_limit = @client.gas_limit
  @gas_price = @client.gas_price
end
truffle_paths() click to toggle source

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

@return [Array<String>] Returns the array containing the list of lookup paths.

# File lib/evm_client/contract.rb, line 275
def self.truffle_paths()
  @truffle_paths = [] unless @truffle_paths
  @truffle_paths
end
truffle_paths=(paths) click to toggle source

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

@param paths [Array<String>, nil] The array containing the list of lookup paths; a `nil` value is

converted to the empty array.
# File lib/evm_client/contract.rb, line 285
def self.truffle_paths=(paths)
  @truffle_paths = (paths.is_a?(Array)) ? paths : []
end

Public Instance Methods

address=(addr) click to toggle source
# File lib/evm_client/contract.rb, line 96
def address=(addr)
  @address = addr
  @events.each do |event|
    event.set_address(addr)
    event.set_client(@client)
  end
end
build() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/evm_client/contract.rb, line 116
def send_transaction(tx_args)
    @client.eth_send_transaction(tx_args)["result"]
end
transact(fun, *args) click to toggle source
# 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) click to toggle source
# 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

Private Instance Methods

add_gas_options_args(args) click to toggle source
# File lib/evm_client/contract.rb, line 312
def add_gas_options_args(args)
  args[:gas] = @client.int_to_hex(gas_limit) if gas_limit.present?
  args[:gasPrice] = @client.int_to_hex(gas_price) if gas_price.present?
  args
end
create_event_proxies() click to toggle source
# File lib/evm_client/contract.rb, line 330
def create_event_proxies
  parent = self
  new_filter_proxy, get_filter_logs_proxy, get_filter_change_proxy = Class.new, Class.new, Class.new
  events.each do |evt|
    new_filter_proxy.send(:define_method, evt.name.underscore) { |*args| parent.create_filter(evt, *args) }
    get_filter_logs_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_logs(evt, *args) }
    get_filter_change_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_changes(evt, *args) }
  end
  @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
end
create_function_proxies() click to toggle source
# File lib/evm_client/contract.rb, line 318
def create_function_proxies
  parent = self
  call_raw_proxy, call_proxy, transact_proxy, transact_and_wait_proxy = Class.new, Class.new, Class.new, Class.new
  @functions.each do |fun|
    call_raw_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call_raw(fun, *args) }
    call_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call(fun, *args) }
    transact_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact(fun, *args) }
    transact_and_wait_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact_and_wait(fun, *args) }
  end
  @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
end