Class: Platon::Contract

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of Contract.



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

def initialize(name, code, abi, client = Platon::Singleton.instance)
  @name = name
  @code = code
  @abi = abi
  @constructor_inputs, @functions, @events = Platon::Abi.parse_abi(abi)
  @formatter = Platon::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.



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

def abi
  @abi
end

#addressObject

Returns the value of attribute address.



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

def address
  @address
end

#call_proxyObject

Returns the value of attribute call_proxy.



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

def call_proxy
  @call_proxy
end

#call_raw_proxyObject

Returns the value of attribute call_raw_proxy.



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

def call_raw_proxy
  @call_raw_proxy
end

#class_objectObject

Returns the value of attribute class_object.



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

def class_object
  @class_object
end

#clientObject

Returns the value of attribute client.



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

def client
  @client
end

#codeObject

Returns the value of attribute code.



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

def code
  @code
end

#constructor_inputsObject

Returns the value of attribute constructor_inputs.



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

def constructor_inputs
  @constructor_inputs
end

#deploymentObject

Returns the value of attribute deployment.



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

def deployment
  @deployment
end

#eventsObject

Returns the value of attribute events.



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

def events
  @events
end

#functionsObject

Returns the value of attribute functions.



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

def functions
  @functions
end

#gas_limitObject

Returns the value of attribute gas_limit.



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

def gas_limit
  @gas_limit
end

#gas_priceObject

Returns the value of attribute gas_price.



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

def gas_price
  @gas_price
end

#get_filter_change_proxyObject

Returns the value of attribute get_filter_change_proxy.



12
13
14
# File 'lib/platon/contract.rb', line 12

def get_filter_change_proxy
  @get_filter_change_proxy
end

#get_filter_logs_proxyObject

Returns the value of attribute get_filter_logs_proxy.



12
13
14
# File 'lib/platon/contract.rb', line 12

def get_filter_logs_proxy
  @get_filter_logs_proxy
end

#keyObject

Returns the value of attribute key.



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

def key
  @key
end

#nameObject

Returns the value of attribute name.



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

def name
  @name
end

#new_filter_proxyObject

Returns the value of attribute new_filter_proxy.



12
13
14
# File 'lib/platon/contract.rb', line 12

def new_filter_proxy
  @new_filter_proxy
end

#nonceObject

Returns the value of attribute nonce.



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

def nonce
  @nonce
end

#senderObject

Returns the value of attribute sender.



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

def sender
  @sender
end

#transact_and_wait_proxyObject

Returns the value of attribute transact_and_wait_proxy.



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

def transact_and_wait_proxy
  @transact_and_wait_proxy
end

#transact_proxyObject

Returns the value of attribute transact_proxy.



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

def transact_proxy
  @transact_proxy
end

Class Method Details

.create(file: nil, client: Platon::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil) ⇒ Platon::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:



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
95
# File 'lib/platon/contract.rb', line 55

def self.create(file: nil, client: Platon::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil)
  contract = nil
  if file.present?
    contracts = Platon::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
                      (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 = Platon::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`.



305
306
307
308
309
310
311
312
313
314
# File 'lib/platon/contract.rb', line 305

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.



280
281
282
283
# File 'lib/platon/contract.rb', line 280

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.



290
291
292
# File 'lib/platon/contract.rb', line 290

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

Instance Method Details

#buildObject



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/platon/contract.rb', line 247

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
    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
    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
  Platon::Contract.send(:remove_const, class_name) if Platon::Contract.const_defined?(class_name, false)
  Platon::Contract.const_set(class_name, class_methods)
  @class_object = class_methods
end

#call(fun, *args) ⇒ Object



184
185
186
187
188
189
190
191
# File 'lib/platon/contract.rb', line 184

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



172
173
174
175
176
# File 'lib/platon/contract.rb', line 172

def call_args(fun, args)
  # add_gas_options_args({to: @address, from: @sender, data: call_payload(fun, args)})
  {to: @address, from: @sender, data: call_payload(fun, args)}
  ## TODO[from js SDK] :missing "from" should give error on deploy and send, call ?
end

#call_payload(fun, args) ⇒ Object



168
169
170
# File 'lib/platon/contract.rb', line 168

def call_payload(fun, args)
  "0x" + fun.signature + (@encoder.encode_arguments(fun.inputs, args).presence || "0"*64)
end

#call_raw(fun, *args) ⇒ Object



178
179
180
181
182
# File 'lib/platon/contract.rb', line 178

def call_raw(fun, *args)
  raw_result = @client.platon_call(call_args(fun, args),"latest")
  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



208
209
210
211
212
213
214
215
216
# File 'lib/platon/contract.rb', line 208

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.platon_new_filter(payload)
  return @decoder.decode_int(filter_id)
end

#deploy(*params) ⇒ Object

Raises:

  • (IOError)


142
143
144
145
146
147
148
149
150
151
# File 'lib/platon/contract.rb', line 142

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 = Platon::Deployment.new(tx, @client)
end

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



153
154
155
156
157
158
159
160
161
# File 'lib/platon/contract.rb', line 153

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



113
114
115
# File 'lib/platon/contract.rb', line 113

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

#deploy_payload(params) ⇒ Object



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

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



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

def estimate(*params)
  result = @client.platon_estimate_gas(deploy_args(params))
  @decoder.decode_int(result.to_s(16))  ## TODO
end

#function_name(fun) ⇒ Object



241
242
243
244
245
# File 'lib/platon/contract.rb', line 241

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



237
238
239
# File 'lib/platon/contract.rb', line 237

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

#get_filter_logs(evt, filter_id) ⇒ Object



233
234
235
# File 'lib/platon/contract.rb', line 233

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

#parse_filter_data(evt, logs) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/platon/contract.rb', line 218

def parse_filter_data(evt, logs)
  formatter = Platon::Formatter.new
  collection = []
  logs.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



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/platon/contract.rb', line 121

def send_raw_transaction(payload, to = nil)
  # Platon.configure { |c| c.chain_id = @client.net_version.to_i }
  @nonce = @client.get_nonce(key.bech32_address(hrp:@client.hrp))
  args = {
    from: key.address,
    value: 0,
    data: payload,
    nonce: @nonce,
    gas_limit: gas_limit,
    gas_price: gas_price,
    chain_id: @client.chain_id
  }
  args[:to] = Utils.decode_bech32_address(to) if to

  puts args

  tx = Platon::Tx.new(args)
  tx.sign key
  @client.platon_send_raw_transaction(tx.hex)
end

#send_transaction(tx_args) ⇒ Object



117
118
119
# File 'lib/platon/contract.rb', line 117

def send_transaction(tx_args)
    @client.platon_send_transaction(tx_args)
end

#transact(fun, *args) ⇒ Object



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

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 Platon::Transaction.new(tx, @client, call_payload(fun, args), args)
end

#transact_and_wait(fun, *args) ⇒ Object



202
203
204
205
206
# File 'lib/platon/contract.rb', line 202

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