platon 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
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