platon 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +216 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/doc/zh-cn.md +1762 -0
- data/lib/bech32.rb +82 -0
- data/lib/platon.rb +77 -0
- data/lib/platon/abi.rb +32 -0
- data/lib/platon/address.rb +62 -0
- data/lib/platon/client.rb +175 -0
- data/lib/platon/contract.rb +351 -0
- data/lib/platon/contract_event.rb +24 -0
- data/lib/platon/contract_initializer.rb +54 -0
- data/lib/platon/decoder.rb +99 -0
- data/lib/platon/deployment.rb +49 -0
- data/lib/platon/encoder.rb +120 -0
- data/lib/platon/explorer_url_helper.rb +0 -0
- data/lib/platon/formatter.rb +142 -0
- data/lib/platon/function.rb +36 -0
- data/lib/platon/function_input.rb +13 -0
- data/lib/platon/function_output.rb +14 -0
- data/lib/platon/gas.rb +9 -0
- data/lib/platon/http_client.rb +55 -0
- data/lib/platon/initializer.rb +0 -0
- data/lib/platon/ipc_client.rb +45 -0
- data/lib/platon/key.rb +105 -0
- data/lib/platon/key/decrypter.rb +113 -0
- data/lib/platon/key/encrypter.rb +128 -0
- data/lib/platon/open_ssl.rb +267 -0
- data/lib/platon/ppos.rb +344 -0
- data/lib/platon/railtie.rb +0 -0
- data/lib/platon/secp256k1.rb +7 -0
- data/lib/platon/sedes.rb +40 -0
- data/lib/platon/segwit_addr.rb +66 -0
- data/lib/platon/singleton.rb +39 -0
- data/lib/platon/solidity.rb +40 -0
- data/lib/platon/transaction.rb +41 -0
- data/lib/platon/tx.rb +201 -0
- data/lib/platon/utils.rb +180 -0
- data/lib/platon/version.rb +5 -0
- data/lib/tasks/platon_contract.rake +27 -0
- data/platon-ruby-logo.png +0 -0
- data/platon.gemspec +50 -0
- 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
|