-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.cpp
3752 lines (3274 loc) · 176 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file
* @copyright defined in eos/LICENSE
* @defgroup eosclienttool EOSIO Command Line Client Reference
* @brief Tool for sending transactions and querying state from @ref nodeos
* @ingroup eosclienttool
*/
/**
@defgroup eosclienttool
@section intro Introduction to cleos
`cleos` is a command line tool that interfaces with the REST api exposed by @ref nodeos. In order to use `cleos` you will need to
have a local copy of `nodeos` running and configured to load the 'eosio::chain_api_plugin'.
cleos contains documentation for all of its commands. For a list of all commands known to cleos, simply run it with no arguments:
```
$ ./cleos
Command Line Interface to EOSIO Client
Usage: programs/cleos/cleos [OPTIONS] SUBCOMMAND
Options:
-h,--help Print this help message and exit
-u,--url TEXT=http://localhost:8888/
the http/https URL where nodeos is running
--wallet-url TEXT=http://localhost:8888/
the http/https URL where keosd is running
-r,--header pass specific HTTP header, repeat this option to pass multiple headers
-n,--no-verify don't verify peer certificate when using HTTPS
-v,--verbose output verbose actions on error
Subcommands:
version Retrieve version information
create Create various items, on and off the blockchain
get Retrieve various items and information from the blockchain
set Set or update blockchain state
transfer Transfer tokens from account to account
net Interact with local p2p network connections
wallet Interact with local wallet
sign Sign a transaction
push Push arbitrary transactions to the blockchain
multisig Multisig contract commands
```
To get help with any particular subcommand, run it with no arguments as well:
```
$ ./cleos create
Create various items, on and off the blockchain
Usage: ./cleos create SUBCOMMAND
Subcommands:
key Create a new keypair and print the public and private keys
account Create a new account on the blockchain (assumes system contract does not restrict RAM usage)
$ ./cleos create account
Create a new account on the blockchain (assumes system contract does not restrict RAM usage)
Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey
Positionals:
creator TEXT The name of the account creating the new account
name TEXT The name of the new account
OwnerKey TEXT The owner public key for the new account
ActiveKey TEXT The active public key for the new account
Options:
-x,--expiration set the time in seconds before a transaction expires, defaults to 30s
-f,--force-unique force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times
-s,--skip-sign Specify if unlocked wallet keys should be used to sign transaction
-d,--dont-broadcast don't broadcast transaction to the network (just print to stdout)
-p,--permission TEXT ... An account and permission level to authorize, as in 'account@permission' (defaults to 'creator@active')
```
*/
#include <pwd.h>
#include <string>
#include <vector>
#include <regex>
#include <iostream>
#include <fc/crypto/hex.hpp>
#include <fc/variant.hpp>
#include <fc/io/datastream.hpp>
#include <fc/io/json.hpp>
#include <fc/io/console.hpp>
#include <fc/exception/exception.hpp>
#include <fc/variant_object.hpp>
#include <fc/stacktrace.hpp>
#include <eosio/chain/name.hpp>
#include <eosio/chain/config.hpp>
#include <eosio/chain/wast_to_wasm.hpp>
#include <eosio/chain/trace.hpp>
#include <eosio/chain_plugin/chain_plugin.hpp>
#include <eosio/chain_api_plugin/chain_api_plugin_results.hpp>
#include <eosio/chain/contract_types.hpp>
#include <cyberway/chain/cyberway_contract_types.hpp>
#pragma push_macro("N")
#undef N
#include <boost/asio.hpp>
#include <boost/format.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include <boost/filesystem.hpp>
#include <boost/process.hpp>
#include <boost/process/spawn.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/algorithm/string/classification.hpp>
#pragma pop_macro("N")
#include <Inline/BasicTypes.h>
#include <IR/Module.h>
#include <IR/Validate.h>
#include <WASM/WASM.h>
#include <Runtime/Runtime.h>
#include <fc/io/fstream.hpp>
#include "CLI11.hpp"
#include "help_text.hpp"
#include "localize.hpp"
#include "config.hpp"
#include "httpc.hpp"
using namespace std;
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::client::help;
using namespace eosio::client::http;
using namespace eosio::client::localize;
using namespace eosio::client::config;
using namespace boost::filesystem;
using namespace appbase;
using namespace Runtime;
namespace eosio {
using name = chain::name;
using account_name = chain::account_name;
}
static const auto wrap_contract = N(cyber.wrap);
static const auto msig_contract = N(cyber.msig);
static const auto token_contract = N(cyber.token);
static const auto domain_contract = config::domain_account_name;
static const auto declare_names_action = N(declarenames);
static const auto msig_contract_str = name(msig_contract).to_string();
FC_DECLARE_EXCEPTION( explained_exception, 9000000, "explained exception, see error log" );
FC_DECLARE_EXCEPTION( localized_exception, 10000000, "an error occured" );
#define EOSC_ASSERT( TEST, ... ) \
FC_EXPAND_MACRO( \
FC_MULTILINE_MACRO_BEGIN \
if( UNLIKELY(!(TEST)) ) \
{ \
std::cerr << localized( __VA_ARGS__ ) << std::endl; \
FC_THROW_EXCEPTION( explained_exception, #TEST ); \
} \
FC_MULTILINE_MACRO_END \
)
//copy pasta from keosd's main.cpp
bfs::path determine_home_directory()
{
bfs::path home;
struct passwd* pwd = getpwuid(getuid());
if(pwd) {
home = pwd->pw_dir;
}
else {
home = getenv("HOME");
}
if(home.empty())
home = "./";
return home;
}
string url = "http://127.0.0.1:8888/";
string default_wallet_url = "unix://" + (determine_home_directory() / "eosio-wallet" / (string(key_store_executable_name) + ".sock")).string();
string wallet_url; //to be set to default_wallet_url in main
bool no_verify = false;
vector<string> headers;
auto tx_expiration = fc::seconds(30);
const fc::microseconds abi_serializer_max_time = fc::seconds(10); // No risk to client side serialization taking a long time
string tx_ref_block_num_or_id;
bool tx_force_unique = false;
bool tx_dont_broadcast = false;
bool tx_return_packed = false;
bool tx_skip_sign = false;
bool tx_print_json = false;
bool print_request = false;
bool print_response = false;
bool no_auto_keosd = false;
uint8_t tx_max_cpu_usage = 0;
uint32_t tx_max_net_usage = 0;
uint32_t delaysec = 0;
vector<string> bandwidth_provider;
vector<string> ram_providers;
struct resolved_name_info {
string domain;
name account;
vector<string> users;
};
FC_REFLECT(resolved_name_info, (domain)(account)(users))
vector<resolved_name_info> tx_resolved_names;
bool tx_dont_declare_names = false; // it's better to have tx_declare_names, but flag option requires default=false
vector<string> tx_permission;
eosio::client::http::http_context context;
bool have_domain_contract();
bytes variant_to_bin(const account_name& account, const action_name& action, const fc::variant& action_args_var);
void add_name_to_declare(const string& textual_name, const eosio::resolve_names_item& i) {
vector<string> parts;
split(parts, textual_name, boost::algorithm::is_any_of("@"));
auto user = parts[0];
auto domain_acc = i.resolved_domain ? *i.resolved_domain : name(parts[2]); // either domain resolved or user@@acc
auto insert_or_modify = [&](auto&& find_domain_record, auto&& get_default_value) {
auto itr = find_if(tx_resolved_names.begin(), tx_resolved_names.end(), find_domain_record);
if (itr == tx_resolved_names.end()) {
tx_resolved_names.emplace_back(get_default_value());
} else {
const auto& etr = itr->users.end();
if (i.resolved_username && etr == find(itr->users.begin(), etr, user)) {
itr->users.emplace_back(user);
}
}
};
if (i.resolved_domain) {
auto domain = parts.size() < 2 ? user : parts[1]; // ? @domain : user@domain
insert_or_modify([&](auto const& info) {
return info.domain == domain;
}, [&]() -> resolved_name_info {
vector<string> users;
if (i.resolved_username) {
users.emplace_back(user);
}
return {domain, domain_acc, users};
});
} else {
insert_or_modify([&](auto const& info) {
return info.account == domain_acc; // can find some existing domain instead of empty, but it's ok
}, [&]() -> resolved_name_info {
return {"", domain_acc, {user}};
});
}
}
void add_standard_transaction_options(CLI::App* cmd, string default_permission = "") {
CLI::callback_t parse_expiration = [](CLI::results_t res) -> bool {
double value_s;
if (res.size() == 0 || !CLI::detail::lexical_cast(res[0], value_s)) {
return false;
}
tx_expiration = fc::seconds(static_cast<uint64_t>(value_s));
return true;
};
cmd->add_option("-x,--expiration", parse_expiration, localized("set the time in seconds before a transaction expires, defaults to 30s"));
cmd->add_flag("-f,--force-unique", tx_force_unique, localized("force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times"));
cmd->add_flag("-s,--skip-sign", tx_skip_sign, localized("Specify if unlocked wallet keys should be used to sign transaction"));
cmd->add_flag("-j,--json", tx_print_json, localized("print result as json"));
cmd->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout)"));
cmd->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction"));
cmd->add_option("-r,--ref-block", tx_ref_block_num_or_id, (localized("set the reference block num or block id used for TAPOS (Transaction as Proof-of-Stake)")));
string msg = "An account and permission level to authorize, as in 'account@permission'";
if (!default_permission.empty())
msg += " (defaults to '" + default_permission + "')";
cmd->add_option("-p,--permission", tx_permission, localized(msg.c_str()));
cmd->add_option("--max-cpu-usage-ms", tx_max_cpu_usage, localized("set an upper limit on the milliseconds of cpu usage budget, for the execution of the transaction (defaults to 0 which means no limit)"));
cmd->add_option("--max-net-usage", tx_max_net_usage, localized("set an upper limit on the net usage budget, in bytes, for the transaction (defaults to 0 which means no limit)"));
cmd->add_option("--delay-sec", delaysec, localized("set the delay_sec seconds, defaults to 0s"));
cmd->add_option("--bandwidth-provider", bandwidth_provider, localized("set an account which provide own bandwidth for transaction"));
cmd->add_flag("--dont-declare-names", tx_dont_declare_names, localized("don't add `declarenames` action for resolved account names"));
}
vector<chain::permission_level> get_account_permissions(const vector<string>& permissions) {
auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) {
vector<string> pieces;
split(pieces, p, boost::algorithm::is_any_of("@"));
if( pieces.size() == 1 ) pieces.push_back( "active" );
return chain::permission_level{ .actor = pieces[0], .permission = pieces[1] };
});
vector<chain::permission_level> accountPermissions;
boost::range::copy(fixedPermissions, back_inserter(accountPermissions));
return accountPermissions;
}
typedef vector<std::pair<account_name,chain::permission_level>> bandwidth_providers;
bandwidth_providers get_bandwidth_providers(const vector<string>& providers) {
bandwidth_providers bandwidthProviders;
for( const auto& p : providers ) {
vector<string> pieces;
split(pieces, p, boost::algorithm::is_any_of("/"));
if( pieces.size() != 2 ) {
std::cerr << localized("Bandwidth provider ${p} not in 'account/provider[@permission]' form", ("p", p)) << std::endl;
continue;
}
const auto account = pieces[0];
split(pieces, pieces[1], boost::algorithm::is_any_of("@"));
if (pieces.size() == 1) pieces.push_back( "active" );
bandwidthProviders.emplace_back( account, chain::permission_level{ .actor = pieces[0], .permission = pieces[1] });
}
return bandwidthProviders;
}
struct provide_ram_params {
provide_ram_params(const string& providers) {
vector<string> pieces;
split(pieces, providers, boost::algorithm::is_any_of("/"));
if (pieces.size() != 2) {
std::cerr << localized("Ram provider ${p} not in 'account/provider[@permission]' form", ("p", providers)) << std::endl;
return;
}
actor = pieces[0];
std::vector<string> provider_and_authority;
split(provider_and_authority, pieces[1], boost::algorithm::is_any_of("@"));
provider = provider_and_authority[0];
permission.permission = provider_and_authority.size() == 1 ? "active" : provider_and_authority[1];
permission.actor = provider;
}
account_name provider;
account_name actor;
chain::permission_level permission;
};
vector<chain::permission_level> get_account_permissions(const vector<string>& permissions, const chain::permission_level& default_permission) {
if (permissions.empty())
return vector<chain::permission_level>{default_permission};
else
return get_account_permissions(tx_permission);
}
template<typename T>
fc::variant call( const std::string& url,
const std::string& path,
const T& v ) {
try {
auto sp = std::make_unique<eosio::client::http::connection_param>(context, parse_url(url) + path, no_verify ? false : true, headers);
return eosio::client::http::do_http_call(*sp, fc::variant(v), print_request, print_response );
}
catch(boost::system::system_error& e) {
if(url == ::url)
std::cerr << localized("Failed to connect to nodeos at ${u}; is nodeos running?", ("u", url)) << std::endl;
else if(url == ::wallet_url)
std::cerr << localized("Failed to connect to keosd at ${u}; is keosd running?", ("u", url)) << std::endl;
throw connection_exception(fc::log_messages{FC_LOG_MESSAGE(error, e.what())});
}
}
template<typename T>
fc::variant call( const std::string& path,
const T& v ) { return call( url, path, fc::variant(v) ); }
template<>
fc::variant call( const std::string& url,
const std::string& path) { return call( url, path, fc::variant() ); }
eosio::get_info_results get_info() {
return call(url, get_info_func).as<eosio::get_info_results>();
}
string generate_nonce_string() {
return fc::to_string(fc::time_point::now().time_since_epoch().count());
}
chain::action generate_nonce_action() {
return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count()));
}
void prompt_for_wallet_password(string& pw, const string& name) {
if(pw.size() == 0 && name != "SecureEnclave") {
std::cout << localized("password: ");
fc::set_console_echo(false);
std::getline( std::cin, pw, '\n' );
fc::set_console_echo(true);
}
}
fc::variant determine_required_keys(const signed_transaction& trx) {
// TODO better error checking
//wdump((trx));
const auto& public_keys = call(wallet_url, wallet_public_keys);
auto get_arg = fc::mutable_variant_object
("transaction", (transaction)trx)
("available_keys", public_keys);
const auto& required_keys = call(get_required_keys, get_arg);
return required_keys["required_keys"];
}
void sign_transaction(signed_transaction& trx, fc::variant& required_keys, const chain_id_type& chain_id) {
fc::variants sign_args = {fc::variant(trx), required_keys, fc::variant(chain_id)};
const auto& signed_trx = call(wallet_url, wallet_sign_trx, sign_args);
trx = signed_trx.as<signed_transaction>();
}
fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
auto info = get_info();
if (trx.signatures.size() == 0) { // #5445 can't change txn content if already signed
trx.expiration = info.head_block_time + tx_expiration;
// Set tapos, default to last irreversible block if it's not specified by the user
block_id_type ref_block_id = info.last_irreversible_block_id;
try {
fc::variant ref_block;
if (!tx_ref_block_num_or_id.empty()) {
ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id));
ref_block_id = ref_block["id"].as<block_id_type>();
}
} EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id));
trx.set_reference_block(ref_block_id);
if (tx_force_unique) {
trx.context_free_actions.emplace_back( generate_nonce_action() );
}
trx.max_cpu_usage_ms = tx_max_cpu_usage;
trx.max_net_usage_words = (tx_max_net_usage + 7)/8;
trx.delay_sec = delaysec;
if (!bandwidth_provider.empty()) {
auto providers = get_bandwidth_providers({bandwidth_provider});
for (const auto& prov: providers) {
trx.actions.emplace_back(
vector<chain::permission_level>{prov.second},
cyberway::chain::providebw{prov.second.actor, prov.first} );
}
}
if (!ram_providers.empty()) {
for (const auto& ram_provider : ram_providers) {
const provide_ram_params provide_params(ram_provider);
trx.actions.emplace_back(
vector<chain::permission_level>{provide_params.permission},
cyberway::chain::provideram{provide_params.provider, provide_params.actor});
}
}
bool declare_names = !tx_dont_declare_names && tx_resolved_names.size() > 0;
if (declare_names) {
FC_ASSERT(have_domain_contract(),
"Can't declare resolved names. Either install ${c} contract, or use --dont-declare-names option",
("c", name{domain_contract}.to_string()));
std::sort(tx_resolved_names.begin(), tx_resolved_names.end(), [](const auto& a, const auto& b) {
return a.domain < b.domain || (a.domain == b.domain && a.account < b.account);
});
fc::variant v = fc::mutable_variant_object()("domains", tx_resolved_names);
auto declare_names = action{{}, domain_contract, declare_names_action,
variant_to_bin(domain_contract, declare_names_action, v)};
trx.actions.emplace_back(declare_names);
}
}
if (!tx_skip_sign) {
auto required_keys = determine_required_keys(trx);
sign_transaction(trx, required_keys, info.chain_id);
}
if (!tx_dont_broadcast) {
return call(push_txn_func, packed_transaction(trx, compression));
} else {
if (!tx_return_packed) {
return fc::variant(trx);
} else {
return fc::variant(packed_transaction(trx, compression));
}
}
}
fc::variant push_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
signed_transaction trx;
trx.actions = std::forward<decltype(actions)>(actions);
return push_transaction(trx, extra_kcpu, compression);
}
void print_action( const fc::variant& at ) {
const auto& receipt = at["receipt"];
auto receiver = receipt["receiver"].as_string();
const auto& act = at["act"].get_object();
auto code = act["account"].as_string();
auto func = act["name"].as_string();
auto args = fc::json::to_string( act["data"] );
auto console = at["console"].as_string();
/*
if( name(code) == config::system_account_name && func == "setcode" )
args = args.substr(40)+"...";
if( name(code) == config::system_account_name && func == "setabi" )
args = args.substr(40)+"...";
*/
if( args.size() > 100 ) args = args.substr(0,100) + "...";
cout << "#" << std::setw(14) << right << receiver << " <= " << std::setw(28) << std::left << (code +"::" + func) << " " << args << "\n";
if( console.size() ) {
std::stringstream ss(console);
string line;
std::getline( ss, line );
cout << ">> " << line << "\n";
}
}
//resolver for ABI serializer to decode actions in proposed transaction in multisig contract
auto abi_serializer_resolver = [](const name& account) -> optional<abi_serializer> {
static unordered_map<account_name, optional<abi_serializer> > abi_cache;
auto it = abi_cache.find( account );
if ( it == abi_cache.end() ) {
auto result = call(get_abi_func, fc::mutable_variant_object("account_name", account));
auto abi_results = result.as<eosio::get_abi_results>();
optional<abi_serializer> abis;
if( abi_results.abi.valid() ) {
abis.emplace( *abi_results.abi, abi_serializer_max_time );
} else {
std::cerr << "ABI for contract " << account.to_string() << " not found. Action data will be shown in hex only." << std::endl;
}
abi_cache.emplace( account, abis );
return abis;
}
return it->second;
};
bool have_domain_contract() {
auto abi = abi_serializer_resolver(domain_contract);
return abi && !abi->get_action_type(declare_names_action).empty();
}
template<typename T>
inline fc::variant variant_from_stream(fc::datastream<const char*>& stream) {
T temp;
fc::raw::unpack(stream, temp);
return fc::variant(temp);
}
struct abi_domain_resolver {
std::map<string, optional<name>>& names;
bool resolved = true;
abi_domain_resolver(std::map<string, optional<name>>& names): names(names) {
}
auto name_pack_unpack() {
using T = name;
return std::make_pair<abi_serializer::unpack_function, abi_serializer::pack_function>(
[](fc::datastream<const char*>& s, bool is_array, bool is_optional) -> fc::variant {
return is_array
? variant_from_stream<vector<T>>(s)
: is_optional
? variant_from_stream<optional<T>>(s)
: variant_from_stream<T>(s);
},
[&](const fc::variant& var, fc::datastream<char*>& ds, bool is_array, bool is_optional) {
if (is_array) {
fc::raw::pack(ds, var.as<vector<T>>());
} else if (is_optional) {
fc::raw::pack(ds, var.as<optional<T>>());
} else {
if (var.is_string()) {
auto n = var.as<string>();
// we can't automatically detect, is string like "golos.io" actually domain_name or account_name
// let's mark resolvable names with @: "@domain", "username@domain", "username@@account"
// and normal account names will be "account" as usual (without @)
// TODO: special case: "username@" to get username from the scope of current action's contract
auto at = n.find('@');
if (at != string::npos) {
if (at == 0) {
n = n.substr(1);
}
if (names[n]) { // also stores value in map if not there
fc::raw::pack(ds, *names[n]);
} else {
resolved = false;
}
return;
}
}
fc::raw::pack(ds, var.as<T>());
}
}
);
}
};
bytes variant_to_bin(const account_name& account, const action_name& action, const fc::variant& action_args_var) {
auto abis = abi_serializer_resolver(account);
FC_ASSERT(abis.valid(), "No ABI found for ${contract}", ("contract", account));
auto action_type = abis->get_action_type(action);
FC_ASSERT(!action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)("contract", account));
static std::map<string, optional<name>> resolved_names;
auto domain_abi = abi_domain_resolver(resolved_names);
abis->add_specialized_unpack_pack("name", domain_abi.name_pack_unpack());
auto bin = abis->variant_to_binary(action_type, action_args_var, abi_serializer_max_time);
while (!domain_abi.resolved) {
vector<string> names;
for (const auto& n: domain_abi.names) {
if (!n.second) {
names.emplace_back(n.first);
}
}
FC_ASSERT(names.size(), "SYSTEM: can't find unresolved names");
// process "username@" names. or it's simpler to add postfix at 1st pass of serializer
//...
fc::variant json = call(resolve_names_func, names);
auto res = json.as<eosio::resolve_names_results>();
auto p = 0;
string n;
for (const auto& ni: res) {
n = names[p++];
if (ni.resolved_username) {
domain_abi.names[n] = ni.resolved_username;
} else {
EOS_ASSERT(ni.resolved_domain, domain_name_type_exception, "SYSTEM: name `${n}` resolved to nothing", ("n",n));
EOS_ASSERT(*ni.resolved_domain != name(), domain_name_type_exception,
"Domain ${d} is unlinked, can not use it in action", ("d",n));
domain_abi.names[n] = ni.resolved_domain;
}
add_name_to_declare(n, ni);
}
domain_abi.resolved = true;
bin = abis->variant_to_binary(action_type, action_args_var, abi_serializer_max_time);
}
return bin;
}
fc::variant bin_to_variant( const account_name& account, const action_name& action, const bytes& action_args) {
auto abis = abi_serializer_resolver( account );
FC_ASSERT( abis.valid(), "No ABI found for ${contract}", ("contract", account));
auto action_type = abis->get_action_type( action );
FC_ASSERT( !action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)( "contract", account ));
return abis->binary_to_variant( action_type, action_args, abi_serializer_max_time );
}
fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::default_parser)
{
regex r("^[ \t]*[\{\[]");
if ( !regex_search(file_or_str, r) && fc::is_regular_file(file_or_str) ) {
return fc::json::from_file(file_or_str, ptype);
} else {
return fc::json::from_string(file_or_str, ptype);
}
}
bytes json_or_file_to_bin( const account_name& account, const action_name& action, const string& data_or_filename ) {
fc::variant action_args_var;
if( !data_or_filename.empty() ) {
try {
action_args_var = json_from_file_or_string(data_or_filename, fc::json::relaxed_parser);
} EOS_RETHROW_EXCEPTIONS(action_type_exception, "Fail to parse action JSON data='${data}'", ("data", data_or_filename));
}
return variant_to_bin( account, action, action_args_var );
}
void print_action_tree( const fc::variant& action ) {
print_action( action );
const auto& inline_traces = action["inline_traces"].get_array();
for( const auto& t : inline_traces ) {
print_action_tree( t );
}
}
void print_result( const fc::variant& result ) { try {
if (result.is_object() && result.get_object().contains("processed")) {
const auto& processed = result["processed"];
const auto& transaction_id = processed["id"].as_string();
string status = processed["receipt"].is_object() ? processed["receipt"]["status"].as_string() : "failed";
int64_t net = -1;
int64_t cpu = -1;
if( processed.get_object().contains( "receipt" )) {
const auto& receipt = processed["receipt"];
if( receipt.is_object()) {
net = receipt["net_usage_words"].as_int64() * 8;
cpu = receipt["cpu_usage_us"].as_int64();
}
}
cerr << status << " transaction: " << transaction_id << " ";
if( net < 0 ) {
cerr << "<unknown>";
} else {
cerr << net;
}
cerr << " bytes ";
if( cpu < 0 ) {
cerr << "<unknown>";
} else {
cerr << cpu;
}
cerr << " us\n";
if( status == "failed" ) {
auto soft_except = processed["except"].as<optional<fc::exception>>();
if( soft_except ) {
edump((soft_except->to_detail_string()));
}
} else {
const auto& actions = processed["action_traces"].get_array();
for( const auto& a : actions ) {
print_action_tree( a );
}
wlog( "\rwarning: transaction executed locally, but may not be confirmed by the network yet" );
}
} else {
cerr << fc::json::to_pretty_string( result ) << endl;
}
} FC_CAPTURE_AND_RETHROW( (result) ) }
using std::cout;
void send_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
auto result = push_actions( move(actions), extra_kcpu, compression);
if( tx_print_json ) {
cout << fc::json::to_pretty_string( result ) << endl;
} else {
print_result( result );
}
}
void send_transaction( signed_transaction& trx, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
auto result = push_transaction(trx, extra_kcpu, compression);
if( tx_print_json ) {
cout << fc::json::to_pretty_string( result ) << endl;
} else {
print_result( result );
}
}
chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) {
return action {
get_account_permissions(tx_permission, {creator,config::active_name}),
eosio::chain::newaccount{
.creator = creator,
.name = newaccount,
.owner = eosio::chain::authority{1, {{owner, 1}}, {}},
.active = eosio::chain::authority{1, {{active, 1}}, {}}
}
};
}
chain::action create_action(const vector<permission_level>& authorization, const account_name& code, const action_name& act, const fc::variant& args) {
return chain::action{authorization, code, act, variant_to_bin(code, act, args)};
}
chain::action create_buyram(const name& creator, const name& newaccount, const asset& quantity) {
fc::variant act_payload = fc::mutable_variant_object()
("payer", creator.to_string())
("receiver", newaccount.to_string())
("quant", quantity.to_string());
return create_action(get_account_permissions(tx_permission, {creator,config::active_name}),
config::system_account_name, N(buyram), act_payload);
}
chain::action create_buyrambytes(const name& creator, const name& newaccount, uint32_t numbytes) {
fc::variant act_payload = fc::mutable_variant_object()
("payer", creator.to_string())
("receiver", newaccount.to_string())
("bytes", numbytes);
return create_action(get_account_permissions(tx_permission, {creator,config::active_name}),
config::system_account_name, N(buyrambytes), act_payload);
}
chain::action create_delegate(const name& from, const name& receiver, const asset& stake, bool transfer) {
fc::variant act_payload = fc::mutable_variant_object()
("from", from.to_string())
("receiver", receiver.to_string())
("stake", stake.to_string())
("transfer", transfer);
return create_action(get_account_permissions(tx_permission, {from,config::active_name}),
config::system_account_name, N(delegatebw), act_payload);
}
fc::variant regproducer_variant(const account_name& producer, const public_key_type& key, const string& url, uint16_t location) {
return fc::mutable_variant_object()
("producer", producer)
("producer_key", key)
("url", url)
("location", location)
;
}
chain::action create_open(const string& contract, const name& owner, symbol sym, const name& ram_payer) {
auto open_ = fc::mutable_variant_object
("owner", owner)
("symbol", sym)
("ram_payer", ram_payer);
return action {
get_account_permissions(tx_permission, {ram_payer,config::active_name}),
contract, "open", variant_to_bin( contract, N(open), open_ )
};
}
chain::action create_transfer(const string& contract, const name& sender, const name& recipient, asset amount, const string& memo ) {
auto transfer = fc::mutable_variant_object
("from", sender)
("to", recipient)
("quantity", amount)
("memo", memo);
return action {
get_account_permissions(tx_permission, {sender,config::active_name}),
contract, "transfer", variant_to_bin( contract, N(transfer), transfer )
};
}
chain::action create_setabi(const name& account, const bytes& abi) {
return action {
get_account_permissions(tx_permission, {account,config::active_name}),
setabi{
.account = account,
.abi = abi
}
};
}
chain::action create_setcode(const name& account, const bytes& code) {
return action {
get_account_permissions(tx_permission, {account,config::active_name}),
setcode{
.account = account,
.vmtype = 0,
.vmversion = 0,
.code = code
}
};
}
chain::action create_updateauth(const name& account, const name& permission, const name& parent, const authority& auth) {
return action { get_account_permissions(tx_permission, {account,config::active_name}),
updateauth{account, permission, parent, auth}};
}
chain::action create_deleteauth(const name& account, const name& permission) {
return action { get_account_permissions(tx_permission, {account,config::active_name}),
deleteauth{account, permission}};
}
chain::action create_linkauth(const name& account, const name& code, const name& type, const name& requirement) {
return action { get_account_permissions(tx_permission, {account,config::active_name}),
linkauth{account, code, type, requirement}};
}
chain::action create_unlinkauth(const name& account, const name& code, const name& type) {
return action { get_account_permissions(tx_permission, {account,config::active_name}),
unlinkauth{account, code, type}};
}
authority parse_json_authority(const std::string& authorityJsonOrFile) {
try {
return json_from_file_or_string(authorityJsonOrFile).as<authority>();
} EOS_RETHROW_EXCEPTIONS(authority_type_exception, "Fail to parse Authority JSON '${data}'", ("data",authorityJsonOrFile))
}
authority parse_json_authority_or_key(const std::string& authorityJsonOrFile) {
if (boost::istarts_with(authorityJsonOrFile, "GLS") || boost::istarts_with(authorityJsonOrFile, "PUB_R1")) {
try {
return authority(public_key_type(authorityJsonOrFile));
} EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", authorityJsonOrFile))
} else {
auto result = parse_json_authority(authorityJsonOrFile);
EOS_ASSERT( eosio::chain::validate(result), authority_type_exception, "Authority failed validation! ensure that keys, accounts, and waits are sorted and that the threshold is valid and satisfiable!");
return result;
}
}
asset to_asset( account_name code, const string& s ) {
static map< pair<account_name, eosio::chain::symbol_code>, eosio::chain::symbol> cache;
auto a = asset::from_string( s );
eosio::chain::symbol_code sym = a.get_symbol().to_symbol_code();
auto it = cache.find( make_pair(code, sym) );
auto sym_str = a.symbol_name();
if ( it == cache.end() ) {
auto json = call(get_currency_stats_func, fc::mutable_variant_object("json", false)
("code", code)
("symbol", sym_str)
);
auto obj = json.get_object();
auto obj_it = obj.find( sym_str );
if (obj_it != obj.end()) {
auto result = obj_it->value().as<eosio::get_currency_stats_result>();
auto p = cache.emplace( make_pair( code, sym ), result.max_supply.get_symbol() );
it = p.first;
} else {
EOS_THROW(symbol_type_exception, "Symbol ${s} is not supported by token contract ${c}", ("s", sym_str)("c", code));
}
}
auto expected_symbol = it->second;
if ( a.decimals() < expected_symbol.decimals() ) {
auto factor = expected_symbol.precision() / a.precision();
auto a_old = a;
a = asset( a.get_amount() * factor, expected_symbol );
} else if ( a.decimals() > expected_symbol.decimals() ) {
EOS_THROW(symbol_type_exception, "Too many decimal digits in ${a}, only ${d} supported", ("a", a)("d", expected_symbol.decimals()));
} // else precision matches
return a;
}
inline asset to_asset( const string& s ) {
return to_asset( token_contract, s );
}
struct set_account_permission_subcommand {
name account;
name permission;
string authority_json_or_file;
name parent;
bool add_code;
bool remove_code;
set_account_permission_subcommand(CLI::App* accountCmd) {
auto permissions = accountCmd->add_subcommand("permission", localized("set parameters dealing with account permissions"));
permissions->add_option("account", account, localized("The account to set/delete a permission authority for"))->required();
permissions->add_option("permission", permission, localized("The permission name to set/delete an authority for"))->required();
permissions->add_option("authority", authority_json_or_file, localized("[delete] NULL, [create/update] public key, JSON string or filename defining the authority, [code] contract name"));
permissions->add_option("parent", parent, localized("[create] The permission name of this parents permission, defaults to 'active'"));
permissions->add_flag("--add-code", add_code, localized("[code] add '${code}' permission to specified permission authority", ("code", name(config::eosio_code_name))));
permissions->add_flag("--remove-code", remove_code, localized("[code] remove '${code}' permission from specified permission authority", ("code", name(config::eosio_code_name))));
add_standard_transaction_options(permissions, "account@active");
permissions->set_callback([this] {
EOSC_ASSERT( !(add_code && remove_code), "ERROR: Either --add-code or --remove-code can be set" );
EOSC_ASSERT( (add_code ^ remove_code) || !authority_json_or_file.empty(), "ERROR: authority should be specified unless add or remove code permission" );
authority auth;
bool need_parent = parent.empty() && (permission != name("owner"));
bool need_auth = add_code || remove_code;
if ( !need_auth && boost::iequals(authority_json_or_file, "null") ) {
send_actions( { create_deleteauth(account, permission) } );
return;
}
if ( need_parent || need_auth ) {
fc::variant json = call(get_account_func, fc::mutable_variant_object("account_name", account.to_string()));
auto res = json.as<eosio::get_account_results>();
auto itr = std::find_if(res.permissions.begin(), res.permissions.end(), [&](const auto& perm) {
return perm.perm_name == permission;
});
if ( need_parent ) {
// see if we can auto-determine the proper parent
if ( itr != res.permissions.end() ) {
parent = (*itr).parent;
} else {
// if this is a new permission and there is no parent we default to "active"
parent = name(config::active_name);
}
}
if ( need_auth ) {
auto actor = (authority_json_or_file.empty()) ? account : name(authority_json_or_file);
auto code_name = name(config::eosio_code_name);
if ( itr != res.permissions.end() ) {
// fetch existing authority
auth = std::move((*itr).required_auth);
auto code_perm = permission_level { actor, code_name };
auto itr2 = std::lower_bound(auth.accounts.begin(), auth.accounts.end(), code_perm, [&](const auto& perm_level, const auto& value) {
return perm_level.permission < value; // Safe since valid authorities must order the permissions in accounts in ascending order