tester.cpp 23.4 KB
Newer Older
1
#include <boost/test/unit_test.hpp>
2 3
#include <eosio/testing/tester.hpp>
#include <eosio/chain/asset.hpp>
4
#include <eosio/chain/wast_to_wasm.hpp>
5
#include <eosio/chain/contracts/types.hpp>
6
#include <eosio/chain/contracts/eos_contract.hpp>
7
#include <eosio/chain/contracts/contract_table_objects.hpp>
8
#include <eosio/chain_plugin/chain_plugin.hpp>
9

10 11
#include <eosio.bios/eosio.bios.wast.hpp>
#include <eosio.bios/eosio.bios.abi.hpp>
12

13
#include <fc/utility.hpp>
14
#include <fc/io/json.hpp>
15 16 17 18 19

#include "WAST/WAST.h"
#include "WASM/WASM.h"
#include "IR/Module.h"
#include "IR/Validate.h"
20

21 22
using namespace eosio::chain::contracts;

23
namespace eosio { namespace testing {
24

25 26 27 28 29 30 31 32
   fc::variant_object filter_fields(const fc::variant_object& filter, const fc::variant_object& value) {
      fc::mutable_variant_object res;
      for( auto& entry : filter ) {
         auto it = value.find(entry.key());
         res( it->key(), it->value() );
      }
      return res;
   }
33

34
   void base_tester::init(bool push_genesis, chain_controller::runtime_limits limits) {
35 36 37 38 39
      cfg.block_log_dir      = tempdir.path() / "blocklog";
      cfg.shared_memory_dir  = tempdir.path() / "shared";
      cfg.shared_memory_size = 1024*1024*8;

      cfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000");
D
Daniel Larimer 已提交
40
      cfg.genesis.initial_key = get_public_key( config::system_account_name, "active" );
41
      cfg.limits = limits;
42 43 44 45 46 47 48 49

      for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) {
         if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--binaryen"))
            cfg.wasm_runtime = chain::wasm_interface::vm_type::binaryen;
         else if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--wavm"))
            cfg.wasm_runtime = chain::wasm_interface::vm_type::wavm;
      }

50
      open();
51 52

      if (push_genesis)
53
         push_genesis_block();
54 55
   }

56

57
   void base_tester::init(chain_controller::controller_config config) {
A
Andrianto Lie 已提交
58 59 60 61
      cfg = config;
      open();
   }

62

63
   public_key_type  base_tester::get_public_key( name keyname, string role ) const {
64
      return get_private_key( keyname, role ).get_public_key();
65 66
   }

67

68
   private_key_type base_tester::get_private_key( name keyname, string role ) const {
69 70 71
      return private_key_type::regenerate<fc::ecc::private_key_shim>(fc::sha256::hash(string(keyname)+role));
   }

72

73
   void base_tester::close() {
B
Bart Wyatt 已提交
74
      control.reset();
75
      chain_transactions.clear();
76
   }
77

78

79
   void base_tester::open() {
80
      control.reset( new chain_controller(cfg) );
81
      chain_transactions.clear();
82
      control->applied_block.connect([this]( const block_trace& trace ){
83
         for( const auto& region : trace.block.regions) {
84 85
            for( const auto& cycle : region.cycles_summary ) {
               for ( const auto& shard : cycle ) {
B
Bart Wyatt 已提交
86
                  for( const auto& receipt: shard.transactions ) {
87 88
                     chain_transactions.emplace(receipt.id, receipt);
                  }
89 90 91 92
               }
            }
         }
      });
93 94
   }

95
   signed_block base_tester::push_block(signed_block b) {
96
      control->push_block(b, 2);
97
      return b;
B
Bucky Kittinger 已提交
98
   }
99

100
   signed_block base_tester::_produce_block( fc::microseconds skip_time, uint32_t skip_flag) {
101 102 103 104
      auto head_time = control->head_block_time();
      auto next_time = head_time + skip_time;
      uint32_t slot  = control->get_slot_at_time( next_time );
      auto sch_pro   = control->get_scheduled_producer(slot);
K
Kevin Heifner 已提交
105
      auto priv_key  = get_private_key( sch_pro, "active" );
106

A
Andrianto Lie 已提交
107
      return control->generate_block( next_time, sch_pro, priv_key, skip_flag );
108
   }
109

110
   void base_tester::produce_blocks( uint32_t n ) {
111 112 113 114
      for( uint32_t i = 0; i < n; ++i )
         produce_block();
   }

115

A
Andrianto Lie 已提交
116 117 118
   void base_tester::produce_blocks_until_end_of_round() {
      uint64_t blocks_per_round;
      while(true) {
A
Andrianto Lie 已提交
119
         blocks_per_round = control->get_global_properties().active_producers.producers.size() * config::producer_repetitions;
A
Andrianto Lie 已提交
120 121 122 123 124
         produce_block();
         if (control->head_block_num() % blocks_per_round == (blocks_per_round - 1) ) break;
      }
   }

125

126
  void base_tester::set_transaction_headers( signed_transaction& trx, uint32_t expiration, uint32_t delay_sec ) const {
127
     trx.expiration = control->head_block_time() + fc::seconds(expiration);
128
     trx.set_reference_block( control->head_block_id() );
129

130
     trx.max_net_usage_words = 0; // No limit
131 132
     trx.max_kcpu_usage = 0; // No limit
     trx.delay_sec = delay_sec;
133 134
  }

135

136
   transaction_trace base_tester::create_account( account_name a, account_name creator, bool multisig ) {
137
      signed_transaction trx;
138
      set_transaction_headers(trx);
139

140 141 142 143 144 145 146 147
      authority owner_auth;
      if (multisig) {
         // multisig between account's owner key and creators active permission
         owner_auth = authority(2, {key_weight{get_public_key( a, "owner" ), 1}}, {permission_level_weight{{creator, config::active_name}, 1}});
      } else {
         owner_auth =  authority( get_public_key( a, "owner" ) );
      }

148
      trx.actions.emplace_back( vector<permission_level>{{creator,config::active_name}},
149
                                contracts::newaccount{
150 151
                                   .creator  = creator,
                                   .name     = a,
152
                                   .owner    = owner_auth,
153 154 155
                                   .active   = authority( get_public_key( a, "active" ) ),
                                   .recovery = authority( get_public_key( a, "recovery" ) ),
                                });
156

157
      set_transaction_headers(trx);
158
      trx.sign( get_private_key( creator, "active" ), chain_id_type()  );
159
      return push_transaction( trx );
160
   }
161

162
   transaction_trace base_tester::push_transaction( packed_transaction& trx, uint32_t skip_flag ) { try {
A
Andrianto Lie 已提交
163
      return control->push_transaction( trx, skip_flag );
164
   } FC_CAPTURE_AND_RETHROW( (transaction_header(trx.get_transaction())) ) }
165

166
   transaction_trace base_tester::push_transaction( signed_transaction& trx, uint32_t skip_flag ) { try {
167
      auto ptrx = packed_transaction(trx);
A
Andrianto Lie 已提交
168
      return push_transaction( ptrx, skip_flag );
169
   } FC_CAPTURE_AND_RETHROW( (transaction_header(trx)) ) }
170

171

172
   typename base_tester::action_result base_tester::push_action(action&& act, uint64_t authorizer) {
173 174
      signed_transaction trx;
      if (authorizer) {
175
         act.authorization = vector<permission_level>{{authorizer, config::active_name}};
176
      }
177
      trx.actions.emplace_back(std::move(act));
178
      set_transaction_headers(trx);
179 180 181 182
      if (authorizer) {
         trx.sign(get_private_key(authorizer, "active"), chain_id_type());
      }
      try {
A
Anton Perkov 已提交
183
         push_transaction(trx);
184 185 186 187 188 189 190 191
      } catch (const fc::exception& ex) {
         return error(ex.top_message());
      }
      produce_block();
      BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
      return success();
   }

192

193 194 195
   transaction_trace base_tester::push_action( const account_name& code,
                             const action_name& acttype,
                             const account_name& actor,
196
                             const variant_object& data,
197 198
                             uint32_t expiration,
                             uint32_t delay_sec)
199 200

   { try {
201
      return push_action(code, acttype, vector<account_name>{ actor }, data, expiration, delay_sec);
202 203
   } FC_CAPTURE_AND_RETHROW( (code)(acttype)(actor)(data)(expiration) ) }

204

205 206 207 208
   transaction_trace base_tester::push_action( const account_name& code,
                             const action_name& acttype,
                             const vector<account_name>& actors,
                             const variant_object& data,
209 210
                             uint32_t expiration,
                             uint32_t delay_sec)
211

212
   { try {
213 214 215
      const auto& acnt = control->get_database().get<account_object,by_name>(code);
      auto abi = acnt.get_abi();
      chain::contracts::abi_serializer abis(abi);
216
      auto a = control->get_database().get<account_object,by_name>(code).get_abi();
217 218

      string action_type_name = abis.get_action_type(acttype);
219
      FC_ASSERT( action_type_name != string(), "unknown action type ${a}", ("a",acttype) );
220

221

222 223 224
      action act;
      act.account = code;
      act.name = acttype;
225 226 227
      for (const auto& actor : actors) {
         act.authorization.push_back(permission_level{actor, config::active_name});
      }
228
      act.data = abis.variant_to_binary(action_type_name, data);
229

230 231
      signed_transaction trx;
      trx.actions.emplace_back(std::move(act));
232
      set_transaction_headers(trx, expiration, delay_sec);
233 234 235
      for (const auto& actor : actors) {
         trx.sign(get_private_key(actor, "active"), chain_id_type());
      }
236 237

      return push_transaction(trx);
238
   } FC_CAPTURE_AND_RETHROW( (code)(acttype)(actors)(data)(expiration) ) }
239

240

241
   transaction_trace base_tester::push_reqauth( account_name from, const vector<permission_level>& auths, const vector<private_key_type>& keys ) {
242 243 244 245 246 247 248 249 250 251 252 253 254 255
      variant pretty_trx = fc::mutable_variant_object()
         ("actions", fc::variants({
            fc::mutable_variant_object()
               ("account", name(config::system_account_name))
               ("name", "reqauth")
               ("authorization", auths)
               ("data", fc::mutable_variant_object()
                  ("from", from)
               )
            })
        );

      signed_transaction trx;
      contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver());
256
      set_transaction_headers(trx);
257
      wdump((trx));
258 259 260 261 262
      for(auto iter = keys.begin(); iter != keys.end(); iter++)
         trx.sign( *iter, chain_id_type() );
      return push_transaction( trx );
   }

263

264 265 266 267 268 269 270 271 272 273
    transaction_trace base_tester::push_reqauth(account_name from, string role, bool multi_sig) {
        if (!multi_sig) {
            return push_reqauth(from, vector<permission_level>{{from, config::owner_name}},
                                        {get_private_key(from, role)});
        } else {
            return push_reqauth(from, vector<permission_level>{{from, config::owner_name}},
                                        {get_private_key(from, role), get_private_key( config::system_account_name, "active" )} );
        }
    }

274

275 276
   transaction_trace base_tester::push_dummy(account_name from, const string& v) {
      // use reqauth for a normal action, this could be anything
277 278 279
      variant pretty_trx = fc::mutable_variant_object()
         ("actions", fc::variants({
            fc::mutable_variant_object()
280
               ("account", name(config::system_account_name))
281
               ("name", "reqauth")
282 283 284
               ("authorization", fc::variants({
                  fc::mutable_variant_object()
                     ("actor", from)
285
                     ("permission", name(config::active_name))
286
               }))
287 288 289 290 291
               ("data", fc::mutable_variant_object()
                  ("from", from)
               )
            })
        )
K
Kevin Heifner 已提交
292
        // lets also push a context free action, the multi chain test will then also include a context free action
293 294 295 296
        ("context_free_actions", fc::variants({
            fc::mutable_variant_object()
               ("account", name(config::system_account_name))
               ("name", "nonce")
297 298 299 300 301 302 303
               ("data", fc::mutable_variant_object()
                  ("value", v)
               )
            })
         );

      signed_transaction trx;
304
      contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver());
305
      set_transaction_headers(trx);
306

307
      trx.sign( get_private_key( from, "active" ), chain_id_type() );
308 309
      return push_transaction( trx );
   }
310

311

312
   transaction_trace base_tester::transfer( account_name from, account_name to, string amount, string memo, account_name currency ) {
313
      return transfer( from, to, asset::from_string(amount), memo, currency );
314
   }
315

316

317
   transaction_trace base_tester::transfer( account_name from, account_name to, asset amount, string memo, account_name currency ) {
318 319 320 321 322 323 324 325 326 327 328 329 330
      variant pretty_trx = fc::mutable_variant_object()
         ("actions", fc::variants({
            fc::mutable_variant_object()
               ("account", currency)
               ("name", "transfer")
               ("authorization", fc::variants({
                  fc::mutable_variant_object()
                     ("actor", from)
                     ("permission", name(config::active_name))
               }))
               ("data", fc::mutable_variant_object()
                  ("from", from)
                  ("to", to)
K
Kevin Heifner 已提交
331
                  ("quantity", amount)
332 333 334 335 336
                  ("memo", memo)
               )
            })
         );

337
      signed_transaction trx;
338
      contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver());
339
      set_transaction_headers(trx);
340

341
      trx.sign( get_private_key( from, name(config::active_name).to_string() ), chain_id_type()  );
342
      return push_transaction( trx );
343 344
   }

345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
   transaction_trace base_tester::issue( account_name to, string amount, account_name currency ) {
      variant pretty_trx = fc::mutable_variant_object()
         ("actions", fc::variants({
            fc::mutable_variant_object()
               ("account", currency)
               ("name", "issue")
               ("authorization", fc::variants({
                  fc::mutable_variant_object()
                     ("actor", currency )
                     ("permission", name(config::active_name))
               }))
               ("data", fc::mutable_variant_object()
                  ("to", to)
                  ("quantity", amount)
               )
            })
         );

      signed_transaction trx;
      contracts::abi_serializer::from_variant(pretty_trx, trx, get_resolver());
366
      set_transaction_headers(trx);
367 368 369 370 371

      trx.sign( get_private_key( currency, name(config::active_name).to_string() ), chain_id_type()  );
      return push_transaction( trx );
   }

372

373 374 375 376 377
   void base_tester::link_authority( account_name account, account_name code, permission_name req, action_name type ) {
      signed_transaction trx;

      trx.actions.emplace_back( vector<permission_level>{{account, config::active_name}},
                                contracts::linkauth(account, code, type, req));
378
      set_transaction_headers(trx);
379 380 381 382 383
      trx.sign( get_private_key( account, "active" ), chain_id_type()  );

      push_transaction( trx );
   }

384

385 386 387 388 389
   void base_tester::unlink_authority( account_name account, account_name code, action_name type ) {
      signed_transaction trx;

      trx.actions.emplace_back( vector<permission_level>{{account, config::active_name}},
                                contracts::unlinkauth(account, code, type ));
390
      set_transaction_headers(trx);
391 392 393 394 395
      trx.sign( get_private_key( account, "active" ), chain_id_type()  );

      push_transaction( trx );
   }

396

397
   void base_tester::set_authority( account_name account,
398 399
                               permission_name perm,
                               authority auth,
400 401 402
                               permission_name parent,
                               const vector<permission_level>& auths,
                               const vector<private_key_type>& keys) { try {
403
      signed_transaction trx;
404 405

      trx.actions.emplace_back( auths,
406 407 408
                                contracts::updateauth{
                                   .account    = account,
                                   .permission = perm,
409 410
                                   .parent     = parent,
                                   .data       = move(auth),
411 412
                                });

413
         set_transaction_headers(trx);
414 415 416 417
      for (const auto& key: keys) {
         trx.sign( key, chain_id_type()  );
      }

418
      push_transaction( trx );
419 420
   } FC_CAPTURE_AND_RETHROW( (account)(perm)(auth)(parent) ) }

421

422 423 424 425 426 427 428 429
   void base_tester::set_authority( account_name account,
                                    permission_name perm,
                                    authority auth,
                                    permission_name parent) {
      set_authority(account, perm, auth, parent, { { account, config::owner_name } }, { get_private_key( account, "owner" ) });
   }


430

431 432 433 434 435 436 437 438
   void base_tester::delete_authority( account_name account,
                                    permission_name perm,
                                    const vector<permission_level>& auths,
                                    const vector<private_key_type>& keys ) { try {
         signed_transaction trx;
         trx.actions.emplace_back( auths,
                                   contracts::deleteauth(account, perm) );

439
         set_transaction_headers(trx);
440 441 442 443 444 445 446
         for (const auto& key: keys) {
            trx.sign( key, chain_id_type()  );
         }

         push_transaction( trx );
      } FC_CAPTURE_AND_RETHROW( (account)(perm) ) }

447

448 449 450 451 452
   void base_tester::delete_authority( account_name account,
                                       permission_name perm ) {
      delete_authority(account, perm, { permission_level{ account, config::owner_name } }, { get_private_key( account, "owner" ) });
   }

453

454
   void base_tester::set_code( account_name account, const char* wast ) try {
455
      set_code(account, wast_to_wasm(wast));
456
   } FC_CAPTURE_AND_RETHROW( (account) )
457

458

459
   void base_tester::set_code( account_name account, const vector<uint8_t> wasm ) try {
460 461 462 463 464 465 466 467 468
      signed_transaction trx;
      trx.actions.emplace_back( vector<permission_level>{{account,config::active_name}},
                                contracts::setcode{
                                   .account    = account,
                                   .vmtype     = 0,
                                   .vmversion  = 0,
                                   .code       = bytes(wasm.begin(), wasm.end())
                                });

469
      set_transaction_headers(trx);
470
      trx.sign( get_private_key( account, "active" ), chain_id_type()  );
471
      push_transaction( trx );
472
   } FC_CAPTURE_AND_RETHROW( (account) )
473

474

475
   void base_tester::set_abi( account_name account, const char* abi_json) {
476 477 478 479 480 481 482 483
      auto abi = fc::json::from_string(abi_json).template as<contracts::abi_def>();
      signed_transaction trx;
      trx.actions.emplace_back( vector<permission_level>{{account,config::active_name}},
                                contracts::setabi{
                                   .account    = account,
                                   .abi        = abi
                                });

484
      set_transaction_headers(trx);
485
      trx.sign( get_private_key( account, "active" ), chain_id_type()  );
486
      push_transaction( trx );
487 488
   }

489

490
   bool base_tester::chain_has_transaction( const transaction_id_type& txid ) const {
491 492 493
      return chain_transactions.count(txid) != 0;
   }

494

495
   const transaction_receipt& base_tester::get_transaction_receipt( const transaction_id_type& txid ) const {
496 497 498
      return chain_transactions.at(txid);
   }

D
Daniel Larimer 已提交
499 500 501
   /**
    *  Reads balance as stored by generic_currency contract
    */
502

503
   asset base_tester::get_currency_balance( const account_name& code,
K
Khaled Al-Hassanieh 已提交
504 505
                                       const symbol&       asset_symbol,
                                       const account_name& account ) const {
D
Daniel Larimer 已提交
506
      const auto& db  = control->get_database();
507
      const auto* tbl = db.template find<contracts::table_id_object, contracts::by_code_scope_table>(boost::make_tuple(code, account, N(accounts)));
508 509 510 511
      share_type result = 0;

      // the balance is implied to be 0 if either the table or row does not exist
      if (tbl) {
512
         const auto *obj = db.template find<contracts::key_value_object, contracts::by_scope_primary>(boost::make_tuple(tbl->id, asset_symbol.to_symbol_code().value));
513
         if (obj) {
514
            //balance is the second field after symbol, so skip the symbol
515
            fc::datastream<const char *> ds(obj->value.data(), obj->value.size());
516 517 518
            fc::raw::unpack(ds, result);
         }
      }
K
Khaled Al-Hassanieh 已提交
519
      return asset(result, asset_symbol);
D
Daniel Larimer 已提交
520 521
   }

522

523
   vector<char> base_tester::get_row_by_account( uint64_t code, uint64_t scope, uint64_t table, const account_name& act ) {
524
      vector<char> data;
525 526
      const auto& db = control->get_database();
      const auto* t_id = db.find<chain::contracts::table_id_object, chain::contracts::by_code_scope_table>( boost::make_tuple( code, scope, table ) );
527 528 529 530
      if ( !t_id ) {
         return data;
      }
      //FC_ASSERT( t_id != 0, "object not found" );
531 532 533 534

      const auto& idx = db.get_index<chain::contracts::key_value_index, chain::contracts::by_scope_primary>();

      auto itr = idx.lower_bound( boost::make_tuple( t_id->id, act ) );
535 536 537
      if ( itr == idx.end() || itr->t_id != t_id->id || act.value != itr->primary_key ) {
         return data;
      }
538 539 540 541 542

      chain_apis::read_only::copy_inline_row( *itr, data );
      return data;
   }

543

544
   vector<uint8_t> base_tester::to_uint8_vector(const string& s) {
545 546 547 548 549
      vector<uint8_t> v(s.size());
      copy(s.begin(), s.end(), v.begin());
      return v;
   };

550

551
   vector<uint8_t> base_tester::to_uint8_vector(uint64_t x) {
552 553 554 555 556
      vector<uint8_t> v(sizeof(x));
      *reinterpret_cast<uint64_t*>(v.data()) = x;
      return v;
   };

557

558
   uint64_t base_tester::to_uint64(fc::variant x) {
559 560 561 562 563 564
      vector<uint8_t> blob;
      fc::from_variant<uint8_t>(x, blob);
      FC_ASSERT(8 == blob.size());
      return *reinterpret_cast<uint64_t*>(blob.data());
   }

565

566
   string base_tester::to_string(fc::variant x) {
567 568 569 570 571 572
      vector<uint8_t> v;
      fc::from_variant<uint8_t>(x, v);
      string s(v.size(), 0);
      copy(v.begin(), v.end(), s.begin());
      return s;
   }
573 574


A
Andrianto Lie 已提交
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
   void base_tester::sync_with(base_tester& other) {
      // Already in sync?
      if (control->head_block_id() == other.control->head_block_id())
         return;
      // If other has a longer chain than we do, sync it to us first
      if (control->head_block_num() < other.control->head_block_num())
         return other.sync_with(*this);

      auto sync_dbs = [](base_tester& a, base_tester& b) {
         for (int i = 1; i <= a.control->head_block_num(); ++i) {
            auto block = a.control->fetch_block_by_number(i);
            if (block && !b.control->is_known_block(block->id())) {
               b.control->push_block(*block, eosio::chain::validation_steps::created_block);
            }
         }
      };

      sync_dbs(*this, other);
      sync_dbs(other, *this);
   }

596
   void base_tester::push_genesis_block() {
597 598
      set_code(config::system_account_name, eosio_bios_wast);
      set_abi(config::system_account_name, eosio_bios_abi);
599
      //produce_block();
600
   }
D
Daniel Larimer 已提交
601

602
   producer_schedule_type base_tester::set_producers(const vector<account_name>& producer_names, const uint32_t version) {
603 604 605
      // Create producer schedule
      producer_schedule_type schedule;
      schedule.version = version;
A
Andrianto Lie 已提交
606
      for (auto& producer_name: producer_names) {
607 608
         producer_key pk = { producer_name, get_public_key( producer_name, "active" )};
         schedule.producers.emplace_back(pk);
A
Andrianto Lie 已提交
609 610
      }

611 612 613 614
      push_action(N(eosio), N(setprods), N(eosio),
                  fc::mutable_variant_object()("version", schedule.version)("producers", schedule.producers));

      return schedule;
A
Andrianto Lie 已提交
615 616
   }

617 618 619 620 621
   const contracts::table_id_object* base_tester::find_table( name code, name scope, name table ) {
      auto tid = control->get_database().find<table_id_object, by_code_scope_table>(boost::make_tuple(code, scope, table));
      return tid;
   }

622
} }  /// eosio::test
623 624 625 626 627 628

std::ostream& operator<<( std::ostream& osm, const fc::variant& v ) {
   //fc::json::to_stream( osm, v );
   osm << fc::json::to_pretty_string( v );
   return osm;
}
629 630 631 632 633 634 635 636 637 638

std::ostream& operator<<( std::ostream& osm, const fc::variant_object& v ) {
   osm << fc::variant(v);
   return osm;
}

std::ostream& operator<<( std::ostream& osm, const fc::variant_object::entry& e ) {
   osm << "{ " << e.key() << ": " << e.value() << " }";
   return osm;
}