platon 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +18 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/Gemfile +12 -0
  7. data/Gemfile.lock +69 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +216 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/doc/zh-cn.md +1762 -0
  14. data/lib/bech32.rb +82 -0
  15. data/lib/platon.rb +77 -0
  16. data/lib/platon/abi.rb +32 -0
  17. data/lib/platon/address.rb +62 -0
  18. data/lib/platon/client.rb +175 -0
  19. data/lib/platon/contract.rb +351 -0
  20. data/lib/platon/contract_event.rb +24 -0
  21. data/lib/platon/contract_initializer.rb +54 -0
  22. data/lib/platon/decoder.rb +99 -0
  23. data/lib/platon/deployment.rb +49 -0
  24. data/lib/platon/encoder.rb +120 -0
  25. data/lib/platon/explorer_url_helper.rb +0 -0
  26. data/lib/platon/formatter.rb +142 -0
  27. data/lib/platon/function.rb +36 -0
  28. data/lib/platon/function_input.rb +13 -0
  29. data/lib/platon/function_output.rb +14 -0
  30. data/lib/platon/gas.rb +9 -0
  31. data/lib/platon/http_client.rb +55 -0
  32. data/lib/platon/initializer.rb +0 -0
  33. data/lib/platon/ipc_client.rb +45 -0
  34. data/lib/platon/key.rb +105 -0
  35. data/lib/platon/key/decrypter.rb +113 -0
  36. data/lib/platon/key/encrypter.rb +128 -0
  37. data/lib/platon/open_ssl.rb +267 -0
  38. data/lib/platon/ppos.rb +344 -0
  39. data/lib/platon/railtie.rb +0 -0
  40. data/lib/platon/secp256k1.rb +7 -0
  41. data/lib/platon/sedes.rb +40 -0
  42. data/lib/platon/segwit_addr.rb +66 -0
  43. data/lib/platon/singleton.rb +39 -0
  44. data/lib/platon/solidity.rb +40 -0
  45. data/lib/platon/transaction.rb +41 -0
  46. data/lib/platon/tx.rb +201 -0
  47. data/lib/platon/utils.rb +180 -0
  48. data/lib/platon/version.rb +5 -0
  49. data/lib/tasks/platon_contract.rake +27 -0
  50. data/platon-ruby-logo.png +0 -0
  51. data/platon.gemspec +50 -0
  52. metadata +235 -0
data/lib/bech32.rb ADDED
@@ -0,0 +1,82 @@
1
+ module Bech32
2
+
3
+ module Encoding
4
+ BECH32 = 1
5
+ BECH32M = 2
6
+ end
7
+
8
+ SEPARATOR = '1'
9
+
10
+ CHARSET = %w(q p z r y 9 x 8 g f 2 t v d w 0 s 3 j n 5 4 k h c e 6 m u a 7 l)
11
+
12
+ BECH32M_CONST = 0x2bc830a3
13
+
14
+ module_function
15
+
16
+ # Returns the encoded Bech32 string.
17
+ def encode(hrp, data, spec)
18
+ checksummed = data + create_checksum(hrp, data, spec)
19
+ hrp + SEPARATOR + checksummed.map{|i|CHARSET[i]}.join
20
+ end
21
+
22
+ # Returns the Bech32 decoded hrp and data.
23
+ def decode(bech, max_length = 90)
24
+ # check uppercase/lowercase
25
+ return nil if bech.bytes.index{|x| x < 33 || x > 126}
26
+ return nil if (bech.downcase != bech && bech.upcase != bech)
27
+ bech = bech.downcase
28
+ # check data length
29
+ pos = bech.rindex(SEPARATOR)
30
+ return nil if pos.nil? || pos + 7 > bech.length || bech.length > max_length
31
+ # check valid charset
32
+ bech[pos+1..-1].each_char{|c|return nil unless CHARSET.include?(c)}
33
+ # split hrp and data
34
+ hrp = bech[0..pos-1]
35
+ data = bech[pos+1..-1].each_char.map{|c|CHARSET.index(c)}
36
+ # check checksum
37
+ spec = verify_checksum(hrp, data)
38
+ spec ? [hrp, data[0..-7], spec] : nil
39
+ end
40
+
41
+ # Returns computed checksum values of +hrp+ and +data+
42
+ def create_checksum(hrp, data, spec)
43
+ values = expand_hrp(hrp) + data
44
+ const = (spec == Bech32::Encoding::BECH32M ? Bech32::BECH32M_CONST : 1)
45
+ polymod = polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
46
+ (0..5).map{|i|(polymod >> 5 * (5 - i)) & 31}
47
+ end
48
+
49
+ # Verify a checksum given Bech32 string
50
+ # @param [String] hrp hrp part.
51
+ # @param [Array[Integer]] data data array.
52
+ # @return [Integer] spec
53
+ def verify_checksum(hrp, data)
54
+ const = polymod(expand_hrp(hrp) + data)
55
+ case const
56
+ when 1
57
+ Encoding::BECH32
58
+ when BECH32M_CONST
59
+ Encoding::BECH32M
60
+ end
61
+ end
62
+
63
+ # Expand the hrp into values for checksum computation.
64
+ def expand_hrp(hrp)
65
+ hrp.each_char.map{|c|c.ord >> 5} + [0] + hrp.each_char.map{|c|c.ord & 31}
66
+ end
67
+
68
+ # Compute Bech32 checksum
69
+ def polymod(values)
70
+ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
71
+ chk = 1
72
+ values.each do |v|
73
+ top = chk >> 25
74
+ chk = (chk & 0x1ffffff) << 5 ^ v
75
+ (0..4).each{|i|chk ^= ((top >> i) & 1) == 0 ? 0 : generator[i]}
76
+ end
77
+ chk
78
+ end
79
+
80
+ private_class_method :polymod, :expand_hrp
81
+
82
+ end
data/lib/platon.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "platon/version"
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
6
+ require 'digest/sha3'
7
+
8
+ require 'ffi'
9
+ require 'money-tree'
10
+ require 'rlp'
11
+
12
+ require 'bech32'
13
+
14
+ module Platon
15
+
16
+ BYTE_ZERO = "\x00".freeze
17
+ UINT_MAX = 2**256 - 1
18
+
19
+ class << self
20
+
21
+ def replayable_chain_id
22
+ 27
23
+ end
24
+
25
+ def v_base
26
+ replayable_chain_id
27
+ end
28
+
29
+ def replayable_v?(v)
30
+ [replayable_chain_id, replayable_chain_id + 1].include? v
31
+ end
32
+
33
+ def chain_id_from_signature(signature)
34
+ return nil if Platon.replayable_v?(signature[:v])
35
+
36
+ cid = (signature[:v] - 35) / 2
37
+ (cid < 1) ? nil : cid
38
+ end
39
+ end
40
+
41
+ require 'platon/abi'
42
+ require 'platon/client'
43
+ require 'platon/ipc_client'
44
+ require 'platon/http_client'
45
+ require 'platon/singleton'
46
+ require 'platon/solidity'
47
+ require 'platon/initializer'
48
+ require 'platon/contract'
49
+ require 'platon/explorer_url_helper'
50
+ require 'platon/function'
51
+ require 'platon/function_input'
52
+ require 'platon/function_output'
53
+ require 'platon/contract_event'
54
+ require 'platon/encoder'
55
+ require 'platon/decoder'
56
+ require 'platon/formatter'
57
+ require 'platon/transaction'
58
+ require 'platon/deployment'
59
+ require 'platon/contract_initializer'
60
+ require 'platon/railtie' if defined?(Rails)
61
+
62
+ require 'platon/ppos'
63
+
64
+ autoload :Address, 'platon/address'
65
+ autoload :Gas, 'platon/gas'
66
+ autoload :Key, 'platon/key'
67
+ autoload :OpenSsl, 'platon/open_ssl'
68
+ autoload :Secp256k1, 'platon/secp256k1'
69
+ autoload :Sedes, 'platon/sedes'
70
+ autoload :Tx, 'platon/tx'
71
+ autoload :Utils, 'platon/utils'
72
+
73
+ autoload :SegwitAddr, 'platon/segwit_addr'
74
+
75
+ class ValidationError < StandardError; end
76
+ class InvalidTransaction < ValidationError; end
77
+ end
data/lib/platon/abi.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Platon
2
+ class Abi
3
+
4
+ def self.parse_abi(abi)
5
+ constructor = abi.detect {|x| x["type"] == "constructor"}
6
+ if constructor.present?
7
+ constructor_inputs = constructor["inputs"].map { |input| Platon::FunctionInput.new(input) }
8
+ else
9
+ constructor_inputs = []
10
+ end
11
+ functions = abi.select {|x| x["type"] == "function" }.map { |fun| Platon::Function.new(fun) }
12
+ events = abi.select {|x| x["type"] == "event" }.map { |evt| Platon::ContractEvent.new(evt) }
13
+ [constructor_inputs, functions, events]
14
+ end
15
+
16
+ def self.parse_type(type)
17
+ raise NotImplementedError if type.ends_with?("]")
18
+ match = /(\D+)(\d.*)?/.match(type)
19
+ [match[1], match[2]]
20
+ end
21
+
22
+ def self.parse_array_type(type)
23
+ match = /(.+)\[(\d*)\]\z/.match(type)
24
+ if match
25
+ [true, match[2].present? ? match[2].to_i : nil, match[1]]
26
+ else
27
+ [false, nil, nil]
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ module Platon
2
+ class Address
3
+
4
+ def initialize(address)
5
+ @address = Utils.prefix_hex(address)
6
+ end
7
+
8
+ def valid?
9
+ if !matches_any_format?
10
+ false
11
+ elsif not_checksummed?
12
+ true
13
+ else
14
+ checksum_matches?
15
+ end
16
+ end
17
+
18
+ def checksummed
19
+ raise "Invalid address: #{address}" unless matches_any_format?
20
+
21
+ cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
22
+ check.match(/[0-7]/) ? char.downcase : char.upcase
23
+ end
24
+
25
+ Utils.prefix_hex(cased.join)
26
+ end
27
+
28
+
29
+ private
30
+
31
+ attr_reader :address
32
+
33
+ def checksum_matches?
34
+ address == checksummed
35
+ end
36
+
37
+ def not_checksummed?
38
+ all_uppercase? || all_lowercase?
39
+ end
40
+
41
+ def all_uppercase?
42
+ address.match(/(?:0[xX])[A-F0-9]{40}/)
43
+ end
44
+
45
+ def all_lowercase?
46
+ address.match(/(?:0[xX])[a-f0-9]{40}/)
47
+ end
48
+
49
+ def matches_any_format?
50
+ address.match(/\A(?:0[xX])[a-fA-F0-9]{40}\z/)
51
+ end
52
+
53
+ def checksum
54
+ Utils.bin_to_hex(Utils.keccak256 unprefixed.downcase)
55
+ end
56
+
57
+ def unprefixed
58
+ Utils.remove_hex_prefix address
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,175 @@
1
+ module Platon
2
+ class Client
3
+
4
+ DEFAULT_GAS_LIMIT = 400_000
5
+
6
+ DEFAULT_GAS_PRICE = 1_000_000_000 #1 GVON
7
+
8
+ RPC_COMMANDS = %w(web3_clientVersion web3_sha3 net_version net_peerCount net_listening platon_protocolVersion platon_syncing platon_gasPrice platon_accounts platon_blockNumber platon_getBalance platon_getStorageAt platon_getTransactionCount platon_getBlockTransactionCountByHash platon_getBlockTransactionCountByNumber platon_getCode platon_sign platon_sendTransaction platon_sendRawTransaction platon_call platon_estimateGas platon_getBlockByHash platon_getBlockByNumber platon_getTransactionByHash platon_getTransactionByBlockHashAndIndex platon_getTransactionByBlockNumberAndIndex platon_getTransactionReceipt platon_getUncleByBlockHashAndIndex platon_newFilter platon_newBlockFilter platon_newPendingTransactionFilter platon_uninstallFilter platon_getFilterChanges platon_getFilterLogs platon_getLogs platon_getWork platon_submitWork platon_submitHashrate db_putString db_getString db_putHex db_getHex shh_post shh_version shh_newIdentity shh_hasIdentity shh_newGroup shh_addToGroup shh_newFilter shh_uninstallFilter shh_getFilterChanges shh_getMessages)
9
+ #PLATON_UNSUPPORT_COMMANDS = %w(platon_coinbase ,plton_mining,platon_hashrate,platon_getUncleCountByBlockHash,platon_getUncleCountByBlockNumber,platon_getUncleByBlockNumberAndIndex platon_getCompilers platon_compileLLL platon_compileSolidity platon_compileSerpent)
10
+ PLATON_RPC_COMMANDS = %w(platon_evidences admin_getProgramVersion admin_getSchnorrNIZKProve)
11
+ RPC_MANAGEMENT_COMMANDS = %w(admin_addPeer admin_datadir admin_nodeInfo admin_peers admin_setSolc admin_startRPC admin_startWS admin_stopRPC admin_stopWS debug_backtraceAt debug_blockProfile debug_cpuProfile debug_dumpBlock debug_gcStats debug_getBlockRlp debug_goTrace debug_memStats debug_seedHash debug_setHead debug_setBlockProfileRate debug_stacks debug_startCPUProfile debug_startGoTrace debug_stopCPUProfile debug_stopGoTrace debug_traceBlock debug_traceBlockByNumber debug_traceBlockByHash debug_traceBlockFromFile debug_traceTransaction debug_verbosity debug_vmodule debug_writeBlockProfile debug_writeMemProfile miner_hashrate miner_makeDAG miner_setExtra miner_setGasPrice miner_start miner_startAutoDAG miner_stop miner_stopAutoDAG personal_importRawKey personal_listAccounts personal_lockAccount personal_newAccount personal_unlockAccount personal_sendTransaction txpool_content txpool_inspect txpool_status)
12
+
13
+ attr_accessor :command, :id, :log, :logger, :default_account, :gas_price, :gas_limit, :ppos, :hrp,:chain_id
14
+
15
+ def initialize(chain_name=nil,log = false)
16
+ @id = 0
17
+ @log = log
18
+ @batch = nil
19
+ # @formatter = Platon::Formatter.new
20
+ @gas_price = DEFAULT_GAS_PRICE
21
+ @gas_limit = DEFAULT_GAS_LIMIT
22
+ if @log == true
23
+ @logger = Logger.new("/tmp/platon_ruby_http.log")
24
+ end
25
+
26
+ @ppos = Platon::Ppos.new self
27
+
28
+ set_network(chain_name.downcase.to_sym)
29
+
30
+ end
31
+
32
+ def self.create(host_or_ipcpath, log = false)
33
+ return IpcClient.new(host_or_ipcpath, log) if host_or_ipcpath.end_with? '.ipc'
34
+ return HttpClient.new(host_or_ipcpath, log) if host_or_ipcpath.start_with? 'http'
35
+ raise ArgumentError.new('Unable to detect client type')
36
+ end
37
+
38
+ def set_network(network)
39
+ config = {
40
+ platondev: {hrp: "lat", chain_id: 210309},
41
+ alaya:{hrp:"atp",chain_id:201018} ,
42
+ alayadev:{hrp:"atp",chain_id: 201030}
43
+ }
44
+ if config[network]
45
+ @hrp = config[network][:hrp]
46
+ @chain_id = config[network][:chain_id]
47
+
48
+ puts "Using Network: #{network}: hrp->#{hrp} , chain_id->#{chain_id}"
49
+ else
50
+ puts "Warning: Network:#{network} not found. You can use 'update_setting' to set hrp & chain_id"
51
+ end
52
+
53
+ end
54
+
55
+ def update_setting(params)
56
+ @hrp = params[:hrp]
57
+ @chain_id = params[:chain_id]
58
+ end
59
+
60
+ def batch
61
+ @batch = []
62
+
63
+ yield
64
+ result = send_batch(@batch)
65
+
66
+ @batch = nil
67
+ reset_id
68
+
69
+ return result
70
+ end
71
+
72
+ def get_id
73
+ @id += 1
74
+ return @id
75
+ end
76
+
77
+ def reset_id
78
+ @id = 0
79
+ end
80
+
81
+ def default_account
82
+ @default_account ||= platon_accounts[0]
83
+ end
84
+
85
+ def int_to_hex(p)
86
+ p.is_a?(Integer) ? "0x#{p.to_s(16)}" : p
87
+ end
88
+
89
+ def encode_params(params)
90
+ params.map(&method(:int_to_hex))
91
+ end
92
+
93
+ def get_nonce(address)
94
+ platon_get_transaction_count(address, "pending")
95
+ end
96
+
97
+ def transfer_to(address, amount)
98
+ platon_send_transaction({to: address, value: int_to_hex(amount)})
99
+ end
100
+
101
+ def transfer_to_and_wait(address, amount)
102
+ wait_for(transfer_to(address, amount))
103
+ end
104
+
105
+
106
+ def transfer(key, bech32_address, amount,other=nil)
107
+ raise ArgumentError.new("#{bech32_address} not match current network hrp :#{hrp}") unless Bech32.decode(bech32_address)[0] == hrp
108
+ args = {
109
+ from: key.address,
110
+ to: Utils.decode_bech32_address(bech32_address),
111
+ value: amount.to_i,
112
+ data: "",
113
+ nonce: get_nonce(key.bech32_address(hrp:hrp)),
114
+ gas_limit: (other && other[:gas_limit])|| gas_limit,
115
+ gas_price: (other && other[:gas_price]) || gas_price,
116
+ chain_id: chain_id
117
+ }
118
+
119
+ tx = Platon::Tx.new(args)
120
+ tx.sign key
121
+ platon_send_raw_transaction(tx.hex)
122
+ end
123
+
124
+ def transfer_and_wait(key, address, amount,other=nil)
125
+ return wait_for(transfer(key, address, amount,other))
126
+ end
127
+
128
+ def wait_for(tx)
129
+ transaction = Platon::Transaction.new(tx, self, "", [])
130
+ transaction.wait_for_miner
131
+ return transaction
132
+ end
133
+
134
+ def send_command(command,args)
135
+
136
+ # if ["platon_call"].include?(command)
137
+ # args << "latest"
138
+ # end
139
+
140
+ payload = {jsonrpc: "2.0", method: command, params: encode_params(args), id: get_id}
141
+ # puts payload
142
+ @logger.info("Sending #{payload.to_json}") if @log
143
+ if @batch
144
+ @batch << payload
145
+ return true
146
+ else
147
+ # tmp = send_single(payload.to_json)
148
+ # output = JSON.parse(tmp)
149
+ output = JSON.parse(send_single(payload.to_json))
150
+ @logger.info("Received #{output.to_json}") if @log
151
+ reset_id
152
+ raise IOError, output["error"]["message"] if output["error"]
153
+
154
+ if %W(net_peerCount platon_protocolVersion platon_gasPrice platon_blockNumber platon_getBalance platon_getTransactionCount platon_getBlockTransactionCountByHash platon_getBlockTransactionCountByNumber platon_estimateGas).include?(command)
155
+ return output["result"].to_i(16)
156
+ end
157
+
158
+ return output["result"]
159
+ end
160
+ end
161
+
162
+ (RPC_COMMANDS + RPC_MANAGEMENT_COMMANDS + PLATON_RPC_COMMANDS).each do |rpc_command|
163
+ method_name = rpc_command.underscore
164
+
165
+ define_method method_name do |*args|
166
+ # puts "res: #{method_name}"
167
+ send_command(rpc_command, args)
168
+ end
169
+ end
170
+
171
+
172
+ end
173
+
174
+ end
175
+
@@ -0,0 +1,351 @@
1
+ require 'forwardable'
2
+
3
+ module Platon
4
+ class Contract
5
+
6
+ attr_reader :address
7
+ attr_accessor :key
8
+ attr_accessor :gas_limit, :gas_price, :nonce
9
+ attr_accessor :code, :name, :abi, :class_object, :sender, :deployment, :client
10
+ attr_accessor :events, :functions, :constructor_inputs
11
+ attr_accessor :call_raw_proxy, :call_proxy, :transact_proxy, :transact_and_wait_proxy
12
+ attr_accessor :new_filter_proxy, :get_filter_logs_proxy, :get_filter_change_proxy
13
+
14
+ def self.hello
15
+ puts 111
16
+ end
17
+
18
+ def initialize(name, code, abi, client = Platon::Singleton.instance)
19
+ @name = name
20
+ @code = code
21
+ @abi = abi
22
+ @constructor_inputs, @functions, @events = Platon::Abi.parse_abi(abi)
23
+ @formatter = Platon::Formatter.new
24
+ @client = client
25
+ @sender = client.default_account
26
+ @encoder = Encoder.new
27
+ @decoder = Decoder.new
28
+ @gas_limit = @client.gas_limit
29
+ @gas_price = @client.gas_price
30
+ end
31
+
32
+ # Creates a contract wrapper.
33
+ # This method attempts to instantiate a contract object from a Solidity source file, from a Truffle
34
+ # artifacts file, or from explicit API and bytecode.
35
+ # - If *:file* is present, the method compiles it and looks up the contract factory at *:contract_index*
36
+ # (0 if not provided). *:abi* and *:code* are ignored.
37
+ # - If *:truffle* is present, the method looks up the Truffle artifacts data for *:name* and uses
38
+ # those data to build a contract instance.
39
+ # - Otherwise, the method uses *:name*, *:code*, and *:abi* to build the contract instance.
40
+ #
41
+ # @param opts [Hash] Options to the method.
42
+ # @option opts [String] :file Path to the Solidity source that contains the contract code.
43
+ # @option opts [Platon::Singleton] :client The client to use.
44
+ # @option opts [String] :code The hex representation of the contract's bytecode.
45
+ # @option opts [Array,String] :abi The contract's ABI; a string is assumed to contain a JSON representation
46
+ # of the ABI.
47
+ # @option opts [String] :address The contract's address; if not present and +:truffle+ is present,
48
+ # the method attempts to determine the address from the artifacts' +networks+ key and the client's
49
+ # network id.
50
+ # @option opts [String] :name The contract name.
51
+ # @option opts [Integer] :contract_index The index of the contract data in the compiled file.
52
+ # @option opts [Hash] :truffle If this parameter is present, the method uses Truffle information to
53
+ # generate the contract wrapper.
54
+ # - *:paths* An array of strings containing the list of paths where to look up Truffle artifacts files.
55
+ # See also {#find_truffle_artifacts}.
56
+ #
57
+ # @return [Platon::Contract] Returns a contract wrapper.
58
+
59
+ def self.create(file: nil, client: Platon::Singleton.instance, code: nil, abi: nil, address: nil, name: nil, contract_index: nil, truffle: nil)
60
+ contract = nil
61
+ if file.present?
62
+ contracts = Platon::Initializer.new(file, client).build_all
63
+ raise "No contracts compiled" if contracts.empty?
64
+ if contract_index
65
+ contract = contracts[contract_index].class_object.new
66
+ else
67
+ contract = contracts.first.class_object.new
68
+ end
69
+ else
70
+ if truffle.present? && truffle.is_a?(Hash)
71
+ artifacts = find_truffle_artifacts(name, (truffle[:paths].is_a?(Array)) ? truffle[:paths] : [])
72
+ if artifacts
73
+ abi = artifacts['abi']
74
+ # The truffle artifacts store bytecodes with a 0x tag, which we need to remove
75
+ # this may need to be 'deployedBytecode'
76
+ code_key = artifacts['bytecode'].present? ? 'bytecode' : 'unlinked_binary'
77
+ code = (artifacts[code_key].start_with?('0x')) ? artifacts[code_key][2, artifacts[code_key].length] : artifacts[code_key]
78
+ unless address
79
+ address = if client
80
+ network_id = client.net_version
81
+ (artifacts['networks'][network_id]) ? artifacts['networks'][network_id]['address'] : nil
82
+ else
83
+ nil
84
+ end
85
+ end
86
+ else
87
+ abi = nil
88
+ code = nil
89
+ end
90
+ else
91
+ abi = abi.is_a?(String) ? JSON.parse(abi) : abi.map(&:deep_stringify_keys)
92
+ end
93
+ contract = Platon::Contract.new(name, code, abi, client)
94
+ contract.build
95
+ contract = contract.class_object.new
96
+ end
97
+ contract.address = address
98
+ contract
99
+ end
100
+
101
+ def address=(addr)
102
+ @address = addr
103
+ @events.each do |event|
104
+ event.set_address(addr)
105
+ event.set_client(@client)
106
+ end
107
+ end
108
+
109
+ def deploy_payload(params)
110
+ if @constructor_inputs.present?
111
+ raise ArgumentError, "Wrong number of arguments in a constructor" and return if params.length != @constructor_inputs.length
112
+ end
113
+ deploy_arguments = @encoder.encode_arguments(@constructor_inputs, params)
114
+ "0x" + @code + deploy_arguments
115
+ end
116
+
117
+ def deploy_args(params)
118
+ add_gas_options_args({from: sender, data: deploy_payload(params)})
119
+ end
120
+
121
+ def send_transaction(tx_args)
122
+ @client.platon_send_transaction(tx_args)
123
+ end
124
+
125
+ def send_raw_transaction(payload, to = nil)
126
+ # Platon.configure { |c| c.chain_id = @client.net_version.to_i }
127
+ @nonce = @client.get_nonce(key.bech32_address)
128
+ args = {
129
+ from: key.address,
130
+ value: 0,
131
+ data: payload,
132
+ nonce: @nonce,
133
+ gas_limit: gas_limit,
134
+ gas_price: gas_price,
135
+ chain_id: @client.chain_id
136
+ }
137
+ args[:to] = Utils.decode_bech32_address(to) if to
138
+
139
+ puts args
140
+
141
+ tx = Platon::Tx.new(args)
142
+ tx.sign key
143
+ @client.platon_send_raw_transaction(tx.hex)
144
+ end
145
+
146
+ def deploy(*params)
147
+ if key
148
+ tx = send_raw_transaction(deploy_payload(params))
149
+ else
150
+ tx = send_transaction(deploy_args(params))
151
+ end
152
+ tx_failed = tx.nil? || tx == "0x0000000000000000000000000000000000000000000000000000000000000000"
153
+ raise IOError, "Failed to deploy, did you unlock #{sender} account? Transaction hash: #{tx}" if tx_failed
154
+ @deployment = Platon::Deployment.new(tx, @client)
155
+ end
156
+
157
+ def deploy_and_wait(*params, **args, &block)
158
+ deploy(*params)
159
+ @deployment.wait_for_deployment(**args, &block)
160
+ self.events.each do |event|
161
+ event.set_address(@address)
162
+ event.set_client(@client)
163
+ end
164
+ @address = @deployment.contract_address
165
+ end
166
+
167
+ def estimate(*params)
168
+ result = @client.platon_estimate_gas(deploy_args(params))
169
+ @decoder.decode_int(result.to_s(16)) ## TODO
170
+ end
171
+
172
+ def call_payload(fun, args)
173
+ "0x" + fun.signature + (@encoder.encode_arguments(fun.inputs, args).presence || "0"*64)
174
+ end
175
+
176
+ def call_args(fun, args)
177
+ # add_gas_options_args({to: @address, from: @sender, data: call_payload(fun, args)})
178
+ {to: @address, from: @sender, data: call_payload(fun, args)}
179
+ ## TODO[from js SDK] :missing "from" should give error on deploy and send, call ?
180
+ end
181
+
182
+ def call_raw(fun, *args)
183
+ raw_result = @client.platon_call(call_args(fun, args),"latest")
184
+ output = @decoder.decode_arguments(fun.outputs, raw_result)
185
+ return {data: call_payload(fun, args), raw: raw_result, formatted: output}
186
+ end
187
+
188
+ def call(fun, *args)
189
+ output = call_raw(fun, *args)[:formatted]
190
+ if output.length == 1
191
+ return output[0]
192
+ else
193
+ return output
194
+ end
195
+ end
196
+
197
+ def transact(fun, *args)
198
+ if key
199
+ tx = send_raw_transaction(call_payload(fun, args), address)
200
+ else
201
+ tx = send_transaction(call_args(fun, args))
202
+ end
203
+ return Platon::Transaction.new(tx, @client, call_payload(fun, args), args)
204
+ end
205
+
206
+ def transact_and_wait(fun, *args)
207
+ tx = transact(fun, *args)
208
+ tx.wait_for_miner
209
+ return tx
210
+ end
211
+
212
+ def create_filter(evt, **params)
213
+ params[:to_block] ||= "latest"
214
+ params[:from_block] ||= "0x0"
215
+ params[:address] ||= @address
216
+ params[:topics] = @encoder.ensure_prefix(evt.signature)
217
+ payload = {topics: [params[:topics]], fromBlock: params[:from_block], toBlock: params[:to_block], address: @encoder.ensure_prefix(params[:address])}
218
+ filter_id = @client.platon_new_filter(payload)
219
+ return @decoder.decode_int(filter_id)
220
+ end
221
+
222
+ def parse_filter_data(evt, logs)
223
+ formatter = Platon::Formatter.new
224
+ collection = []
225
+ logs.each do |result|
226
+ inputs = evt.input_types
227
+ outputs = inputs.zip(result["topics"][1..-1])
228
+ data = {blockNumber: result["blockNumber"].hex, transactionHash: result["transactionHash"], blockHash: result["blockHash"], transactionIndex: result["transactionIndex"].hex, topics: []}
229
+ outputs.each do |output|
230
+ data[:topics] << formatter.from_payload(output)
231
+ end
232
+ collection << data
233
+ end
234
+ return collection
235
+ end
236
+
237
+ def get_filter_logs(evt, filter_id)
238
+ parse_filter_data evt, @client.platon_get_filter_logs(filter_id)
239
+ end
240
+
241
+ def get_filter_changes(evt, filter_id)
242
+ parse_filter_data evt, @client.platon_get_filter_changes(filter_id)
243
+ end
244
+
245
+ def function_name(fun)
246
+ count = functions.select {|x| x.name == fun.name }.count
247
+ name = (count == 1) ? "#{fun.name.underscore}" : "#{fun.name.underscore}__#{fun.inputs.collect {|x| x.type}.join("__")}"
248
+ name.to_sym
249
+ end
250
+
251
+ def build
252
+ class_name = @name.camelize
253
+ parent = self
254
+ create_function_proxies
255
+ create_event_proxies
256
+ class_methods = Class.new do
257
+ extend Forwardable
258
+ def_delegators :parent, :deploy_payload, :deploy_args, :call_payload, :call_args
259
+ def_delegators :parent, :signed_deploy, :key, :key=
260
+ def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
261
+ def_delegators :parent, :abi, :deployment, :events
262
+ def_delegators :parent, :estimate, :deploy, :deploy_and_wait
263
+ def_delegators :parent, :address, :address=, :sender, :sender=
264
+ def_delegator :parent, :call_raw_proxy, :call_raw
265
+ def_delegator :parent, :call_proxy, :call
266
+ def_delegator :parent, :transact_proxy, :transact
267
+ def_delegator :parent, :transact_and_wait_proxy, :transact_and_wait
268
+ def_delegator :parent, :new_filter_proxy, :new_filter
269
+ def_delegator :parent, :get_filter_logs_proxy, :get_filter_logs
270
+ def_delegator :parent, :get_filter_change_proxy, :get_filter_changes
271
+ define_method :parent do
272
+ parent
273
+ end
274
+ end
275
+ Platon::Contract.send(:remove_const, class_name) if Platon::Contract.const_defined?(class_name, false)
276
+ Platon::Contract.const_set(class_name, class_methods)
277
+ @class_object = class_methods
278
+ end
279
+
280
+ # Get the list of paths where to look up Truffle artifacts files.
281
+ #
282
+ # @return [Array<String>] Returns the array containing the list of lookup paths.
283
+
284
+ def self.truffle_paths()
285
+ @truffle_paths = [] unless @truffle_paths
286
+ @truffle_paths
287
+ end
288
+
289
+ # Set the list of paths where to look up Truffle artifacts files.
290
+ #
291
+ # @param paths [Array<String>, nil] The array containing the list of lookup paths; a `nil` value is
292
+ # converted to the empty array.
293
+
294
+ def self.truffle_paths=(paths)
295
+ @truffle_paths = (paths.is_a?(Array)) ? paths : []
296
+ end
297
+
298
+ # Looks up and loads a Truffle artifacts file.
299
+ # This method iterates over the `truffle_path` elements, looking for an artifact file in the
300
+ # `build/contracts` subdirectory.
301
+ #
302
+ # @param name [String] The name of the contract whose artifacts to look up.
303
+ # @param paths [Array<String>] An additional list of paths to look up; this list, if present, is
304
+ # prepended to the `truffle_path`.
305
+ #
306
+ # @return [Hash,nil] Returns a hash containing the parsed JSON from the artifacts file; if no file
307
+ # was found, returns `nil`.
308
+
309
+ def self.find_truffle_artifacts(name, paths = [])
310
+ subpath = File.join('build', 'contracts', "#{name}.json")
311
+
312
+ found = paths.concat(truffle_paths).find { |p| File.file?(File.join(p, subpath)) }
313
+ if (found)
314
+ JSON.parse(IO.read(File.join(found, subpath)))
315
+ else
316
+ nil
317
+ end
318
+ end
319
+
320
+ private
321
+ def add_gas_options_args(args)
322
+ args[:gas] = @client.int_to_hex(gas_limit) if gas_limit.present?
323
+ args[:gasPrice] = @client.int_to_hex(gas_price) if gas_price.present?
324
+ puts args
325
+ args
326
+ end
327
+
328
+ def create_function_proxies
329
+ parent = self
330
+ call_raw_proxy, call_proxy, transact_proxy, transact_and_wait_proxy = Class.new, Class.new, Class.new, Class.new
331
+ @functions.each do |fun|
332
+ call_raw_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call_raw(fun, *args) }
333
+ call_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.call(fun, *args) }
334
+ transact_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact(fun, *args) }
335
+ transact_and_wait_proxy.send(:define_method, parent.function_name(fun)) { |*args| parent.transact_and_wait(fun, *args) }
336
+ end
337
+ @call_raw_proxy, @call_proxy, @transact_proxy, @transact_and_wait_proxy = call_raw_proxy.new, call_proxy.new, transact_proxy.new, transact_and_wait_proxy.new
338
+ end
339
+
340
+ def create_event_proxies
341
+ parent = self
342
+ new_filter_proxy, get_filter_logs_proxy, get_filter_change_proxy = Class.new, Class.new, Class.new
343
+ events.each do |evt|
344
+ new_filter_proxy.send(:define_method, evt.name.underscore) { |*args| parent.create_filter(evt, *args) }
345
+ get_filter_logs_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_logs(evt, *args) }
346
+ get_filter_change_proxy.send(:define_method, evt.name.underscore) { |*args| parent.get_filter_changes(evt, *args) }
347
+ end
348
+ @new_filter_proxy, @get_filter_logs_proxy, @get_filter_change_proxy = new_filter_proxy.new, get_filter_logs_proxy.new, get_filter_change_proxy.new
349
+ end
350
+ end
351
+ end