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.
- 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
|