Class: Eth::Contract

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

Overview

Provides classes to access smart contracts

Defined Under Namespace

Classes: Error, Event, Function, FunctionInput, FunctionOutput, Initializer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, bin, abi) ⇒ Contract

Constructor of the Eth::Contract class.

Note, do not use this directly. Use from_abi, from_bin, or from_file!

Parameters:

  • name (String)

    contract name.

  • bin (String)

    contract bin string.

  • abi (String)

    contract abi string.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/eth/contract.rb', line 38

def initialize(name, bin, abi)

  # The contract name will be the class name and needs title casing.
  _name = name.dup
  _name[0] = name[0].upcase

  @name = _name
  @bin = bin
  @abi = abi
  @constructor_inputs, @functions, @events, @errors = parse_abi(abi)
  @sigs2events = {}
  @events.each do |event|
    @sigs2events[event.signature] = event
  end
end

Instance Attribute Details

#abiObject

Returns the value of attribute abi.



27
28
29
# File 'lib/eth/contract.rb', line 27

def abi
  @abi
end

#addressObject

Returns the value of attribute address.



24
25
26
# File 'lib/eth/contract.rb', line 24

def address
  @address
end

#binObject

Returns the value of attribute bin.



27
28
29
# File 'lib/eth/contract.rb', line 27

def bin
  @bin
end

#class_objectObject

Returns the value of attribute class_object.



27
28
29
# File 'lib/eth/contract.rb', line 27

def class_object
  @class_object
end

#constructor_inputsObject

Returns the value of attribute constructor_inputs.



28
29
30
# File 'lib/eth/contract.rb', line 28

def constructor_inputs
  @constructor_inputs
end

#errorsObject

Returns the value of attribute errors.



28
29
30
# File 'lib/eth/contract.rb', line 28

def errors
  @errors
end

#eventsObject

Returns the value of attribute events.



28
29
30
# File 'lib/eth/contract.rb', line 28

def events
  @events
end

#functionsObject

Returns the value of attribute functions.



28
29
30
# File 'lib/eth/contract.rb', line 28

def functions
  @functions
end

#gas_limitObject

Returns the value of attribute gas_limit.



26
27
28
# File 'lib/eth/contract.rb', line 26

def gas_limit
  @gas_limit
end

#gas_priceObject

Returns the value of attribute gas_price.



26
27
28
# File 'lib/eth/contract.rb', line 26

def gas_price
  @gas_price
end

#keyObject

Returns the value of attribute key.



25
26
27
# File 'lib/eth/contract.rb', line 25

def key
  @key
end

#max_fee_per_gasObject

Returns the value of attribute max_fee_per_gas.



26
27
28
# File 'lib/eth/contract.rb', line 26

def max_fee_per_gas
  @max_fee_per_gas
end

#max_priority_fee_per_gasObject

Returns the value of attribute max_priority_fee_per_gas.



26
27
28
# File 'lib/eth/contract.rb', line 26

def max_priority_fee_per_gas
  @max_priority_fee_per_gas
end

#nameObject

Returns the value of attribute name.



27
28
29
# File 'lib/eth/contract.rb', line 27

def name
  @name
end

#nonceObject

Returns the value of attribute nonce.



26
27
28
# File 'lib/eth/contract.rb', line 26

def nonce
  @nonce
end

Class Method Details

.from_abi(abi:, address:, name:) ⇒ Eth::Contract::Object

Creates a contract wrapper from ABI and address.

Parameters:

  • abi (String)

    contract abi string.

  • address (String)

    contract address.

  • name (String)

    name of contract.

Returns:

  • (Eth::Contract::Object)

    Returns the class of the smart contract.

Raises:

  • (JSON::ParserError)

    if the json format is wrong.

  • (ArgumentError)

    if ABI, address, or name is missing.



75
76
77
78
79
80
81
82
# File 'lib/eth/contract.rb', line 75

def self.from_abi(abi:, address:, name:)
  abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
  contract = Eth::Contract.new(name, nil, abi)
  contract.build
  contract = contract.class_object.new
  contract.address = address
  contract
end

.from_bin(bin:, abi:, name:) ⇒ Eth::Contract::Object

Creates a contract wrapper from binary and ABI.

Parameters:

  • bin (String)

    contract bin string.

  • abi (String)

    contract abi string.

  • name (String)

    name of contract.

Returns:

  • (Eth::Contract::Object)

    Returns the class of the smart contract.

Raises:

  • (JSON::ParserError)

    if the json format is wrong.

  • (ArgumentError)

    if ABI, binary, or name is missing.



92
93
94
95
96
97
# File 'lib/eth/contract.rb', line 92

def self.from_bin(bin:, abi:, name:)
  abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
  contract = Eth::Contract.new(name, bin, abi)
  contract.build
  contract.class_object.new
end

.from_file(file:, contract_index: 0) ⇒ Eth::Contract::Object

Creates a contract wrapper from a Solidity file.

Parameters:

  • file (String)

    solidity file path.

  • contract_index (Number) (defaults to: 0)

    specify contract.

Returns:

  • (Eth::Contract::Object)

    Returns the class of the smart contract.

Raises:

  • (ArgumentError)

    if the file path is empty or no contracts were compiled.



60
61
62
63
64
65
# File 'lib/eth/contract.rb', line 60

def self.from_file(file:, contract_index: 0)
  raise ArgumentError, "Cannot find the contract at #{file.to_s}!" if !File.exist?(file.to_s)
  contracts = Eth::Contract::Initializer.new(file).build_all
  raise ArgumentError, "No contracts compiled." if contracts.empty?
  contracts[contract_index].class_object.new
end

Instance Method Details

#buildObject

Create meta classes for smart contracts.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/eth/contract.rb', line 160

def build
  class_name = @name
  parent = self
  class_methods = Class.new do
    extend Forwardable
    def_delegators :parent, :key, :key=
    def_delegators :parent, :name, :abi, :bin
    def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
    def_delegators :parent, :max_fee_per_gas, :max_fee_per_gas=, :max_priority_fee_per_gas, :max_priority_fee_per_gas=
    def_delegators :parent, :events, :errors
    def_delegators :parent, :address, :address=
    def_delegator :parent, :functions
    def_delegator :parent, :function
    def_delegator :parent, :error
    def_delegator :parent, :decode_error
    def_delegator :parent, :decode_log
    def_delegator :parent, :constructor_inputs
    define_method :parent do
      parent
    end
  end
  Eth::Contract.send(:remove_const, class_name) if Eth::Contract.const_defined?(class_name, false)
  Eth::Contract.const_set(class_name, class_methods)
  @class_object = class_methods
end

#decode_error(rpc_error) ⇒ String

Decodes a custom error returned by an RPC error using the contract ABI.

Parameters:

  • rpc_error (RpcError)

    the RPC error containing revert data.

Returns:

  • (String)

    a human readable error message.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/eth/contract.rb', line 141

def decode_error(rpc_error)
  data = rpc_error.data
  return rpc_error.message if data.nil? || errors.nil?

  signature = data[0, 10]
  if (err = errors.find { |e| e.signature == signature })
    values = err.decode(data)
    args = values&.map { |v| v.is_a?(String) ? v : v.inspect }&.join(",")
    args ||= ""
    "execution reverted: #{err.name}(#{args})"
  elsif signature == "0x08c379a0"
    reason = Abi.decode(["string"], "0x" + data[10..])&.first
    "execution reverted: #{reason}"
  else
    rpc_error.message
  end
end

#decode_log(topics, data) ⇒ Hash

Decodes an event log by looking up the event signature in the contract ABI.

Parameters:

  • topics (Array<String>)

    the list of log topics, including the event selector.

  • data (String)

    the log data containing non-indexed parameters.

Returns:

  • (Hash)

    a hash of decoded event parameters.

Raises:

  • (ArgumentError)

    if the event is not part of the contract ABI.



194
195
196
197
198
199
# File 'lib/eth/contract.rb', line 194

def decode_log(topics, data)
  sig = Eth::Util.remove_hex_prefix(topics[0])
  event = @sigs2events[sig]
  raise ArgumentError, "this event does not exist!" if event.nil?
  event.decode_params(topics, data)
end

#error(name, args: nil) ⇒ Eth::Contract::Error

Finds an error by name.

Parameters:

  • name (String)

    error name.

  • args (Integer, nil) (defaults to: nil)

    number of arguments of an error.

Returns:

Raises:

  • (ArgumentError)

    if error not found.



131
132
133
134
135
# File 'lib/eth/contract.rb', line 131

def error(name, args: nil)
  errors.find do |e|
    e.name == name && (args.nil? || args == e.inputs.size)
  end || raise(ArgumentError, "this error does not exist!")
end

#function(name, args: nil) ⇒ Eth::Contract::Function

Finds a function by name.

Parameters:

  • name (String)

    function name.

  • args (Integer, nil) (defaults to: nil)

    number of arguments of a function.

Returns:

Raises:

  • (ArgumentError)

    if function not found.



119
120
121
122
123
# File 'lib/eth/contract.rb', line 119

def function(name, args: nil)
  functions.find do |f|
    f.name == name && (args.nil? || args == f.inputs.size)
  end || raise(ArgumentError, "this function does not exist!")
end