From c6b848ef18ed80607bfa4e0fde6fbcf38c0f6862 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Sat, 18 Jul 2015 00:52:28 -0400 Subject: [PATCH] implement payment splitter proposal --- libraries/chain/db_init.cpp | 7 + .../graphene/chain/protocol/operations.hpp | 8 +- .../graphene/chain/protocol/splitter.hpp | 147 +++++++++++ .../include/graphene/chain/protocol/types.hpp | 6 +- .../graphene/chain/splitter_evaluator.hpp | 232 ++++++++++++++++++ programs/js_operation_serializer/main.cpp | 1 + 6 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 libraries/chain/include/graphene/chain/protocol/splitter.hpp create mode 100644 libraries/chain/include/graphene/chain/splitter_evaluator.hpp diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 32a724cf..eeb5c984 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -150,6 +151,11 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -177,6 +183,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 853e9c5c..5c1a0b51 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -59,7 +60,12 @@ namespace graphene { namespace chain { custom_operation, assert_operation, balance_claim_operation, - override_transfer_operation + override_transfer_operation, + splitter_create_operation, + splitter_update_operation, + splitter_pay_operation, + splitter_payout_operation, + splitter_delete_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/splitter.hpp b/libraries/chain/include/graphene/chain/protocol/splitter.hpp new file mode 100644 index 00000000..36cafa35 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/splitter.hpp @@ -0,0 +1,147 @@ +/* Copyright (c) 2015, Cryptonomex, Inc. */ +#pragma once +#include + +namespace graphene { namespace chain { + + struct market_buyback + { + asset_id_type asset_to_buy; + price limit_price; + void validate()const { + limit_price.validate(); + FC_ASSERT( limit_price.quote.asset_id == asset_to_buy ); + } + }; + + typedef static_variant payment_target_type; + + struct payment_target + { + uint16_t weight = 0; + payment_target_type target; + }; + + struct payment_target_validate + { + typedef void result_type; + void operator()( const account_id_type& id )const { } + void operator()( const market_buyback& t )const + { + FC_ASSERT( t.asset_to_buy == t.limit_price.quote.asset_id ); + t.limit_price.validate(); + } + }; + + struct splitter_create_operation : public base_operation + { + /// TODO: charge fee based upon size + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type payer; + account_id_type owner; + vector targets; + asset min_payment; + share_type max_payment; ///< same asset_id as min_payment + share_type payout_threshold; ///< same asset_id as min_payment + + void validate()const + { + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( min_payment.amount > 0 ); + FC_ASSERT( min_payment.amount <= max_payment ); + FC_ASSERT( payout_threshold >= 0 ); + for( const auto& t : targets ) + { + FC_ASSERT( t.weight > 0 ); + t.target.visit( payment_target_validate() ); + } + } + account_id_type fee_payer()const { return payer; } + }; + + struct splitter_update_operation : public base_operation + { + /// TODO: charge fee based upon size + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + splitter_id_type splitter_id; + account_id_type owner; ///< must match splitter_id->owner + account_id_type new_owner; + vector targets; + asset min_payment; + share_type max_payment; ///< same asset_id as min_payment + share_type payout_threshold; ///< same asset_id as min_payment + + void validate()const + { + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( min_payment.amount > 0 ); + FC_ASSERT( min_payment.amount <= max_payment ); + FC_ASSERT( payout_threshold >= 0 ); + for( const auto& t : targets ) FC_ASSERT( t.weight > 0 ); + } + + account_id_type fee_payer()const { return owner; } + }; + + struct splitter_pay_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + splitter_id_type splitter_id; + account_id_type paying_account; ///< also fee payer + asset payment; + + void validate()const + { + FC_ASSERT( payment.amount > 0 ); + FC_ASSERT( fee.amount >= 0 ); + } + + account_id_type fee_payer()const { return paying_account; } + }; + + struct splitter_payout_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + splitter_id_type splitter_id; + account_id_type owner; ///< must match splitter_id->owner + + void validate()const { FC_ASSERT( fee.amount >= 0 ); } + account_id_type fee_payer()const { return owner; } + }; + + + struct splitter_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + splitter_id_type splitter_id; + account_id_type owner; ///< must match splitter_id->owner + + void validate()const { FC_ASSERT( fee.amount >= 0 ); } + account_id_type fee_payer()const { return owner; } + }; + +} } + +FC_REFLECT( graphene::chain::market_buyback, (asset_to_buy)(limit_price) ) +FC_REFLECT( graphene::chain::payment_target, (weight)(target) ) +FC_REFLECT( graphene::chain::splitter_create_operation, (fee)(payer)(owner)(targets)(min_payment)(max_payment)(payout_threshold) ) +FC_REFLECT( graphene::chain::splitter_update_operation, (fee)(owner)(new_owner)(targets)(min_payment)(max_payment)(payout_threshold) ) +FC_REFLECT( graphene::chain::splitter_pay_operation, (fee)(splitter_id)(paying_account)(payment) ) +FC_REFLECT( graphene::chain::splitter_payout_operation, (fee)(splitter_id)(owner) ) +FC_REFLECT( graphene::chain::splitter_delete_operation, (fee)(splitter_id)(owner) ) +FC_REFLECT( graphene::chain::splitter_create_operation::fee_parameters_type, (fee) ); +FC_REFLECT( graphene::chain::splitter_update_operation::fee_parameters_type, (fee) ); +FC_REFLECT( graphene::chain::splitter_pay_operation::fee_parameters_type, (fee) ); +FC_REFLECT( graphene::chain::splitter_payout_operation::fee_parameters_type, (fee) ); +FC_REFLECT( graphene::chain::splitter_delete_operation::fee_parameters_type, (fee) ); + diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 64d49325..419accde 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -123,6 +123,7 @@ namespace graphene { namespace chain { vesting_balance_object_type, worker_object_type, balance_object_type, + splitter_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -169,11 +170,12 @@ namespace graphene { namespace chain { class witness_schedule_object; class worker_object; class balance_object; + class splitter_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; typedef object_id< protocol_ids, force_settlement_object_type, force_settlement_object> force_settlement_id_type; - typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; + typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; typedef object_id< protocol_ids, witness_object_type, witness_object> witness_id_type; typedef object_id< protocol_ids, limit_order_object_type, limit_order_object> limit_order_id_type; typedef object_id< protocol_ids, call_order_object_type, call_order_object> call_order_id_type; @@ -184,6 +186,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; + typedef object_id< protocol_ids, splitter_object_type, splitter_object> splitter_id_type; // implementation types class global_property_object; @@ -376,6 +379,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (vesting_balance_object_type) (worker_object_type) (balance_object_type) + (splitter_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, diff --git a/libraries/chain/include/graphene/chain/splitter_evaluator.hpp b/libraries/chain/include/graphene/chain/splitter_evaluator.hpp new file mode 100644 index 00000000..d8d822f2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/splitter_evaluator.hpp @@ -0,0 +1,232 @@ +/* Copyright (c) 2015, Cryptonomex, Inc. */ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + class splitter_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = splitter_object_type; + + account_id_type owner; + asset balance; + vector targets; + asset min_payment; + share_type max_payment; ///< same asset_id as min_payment + share_type payout_threshold; ///< same asset_id as min_payment + + + struct payout_visitor + { + typedef void result_type; + database& db; + asset amount; + + payout_visitor( database& d, asset a ):db(d),amount(a){} + + void operator()( const account_id_type& id )const + { + db.adjust_balance( id(db), amount ); + } + void operator()( const market_buyback& t )const + { + const auto& new_order_object = db.create([&](limit_order_object& obj){ + obj.seller = GRAPHENE_NULL_ACCOUNT; + obj.for_sale = amount.amount; + obj.sell_price = t.limit_price; + assert( amount.asset_id == t.limit_price.base.asset_id ); + }); + db.apply_order(new_order_object); + } + }; + void payout( database& db )const + { + uint64_t total_weight = 0; + for( auto& t : targets ) total_weight += t.weight; + + asset remaining = balance; + + for( uint32_t i = 0 ; i < targets.size(); ++i ) + { + const auto& t = targets[i]; + + fc::uint128 tmp( balance.amount.value ); + tmp *= t.weight; + tmp /= total_weight; + + asset payout_amount( tmp.to_uint64(), balance.asset_id ); + + if( payout_amount > remaining || (i == (targets.size() - 1)) ) + payout_amount = remaining; + + if( payout_amount.amount > 0 ) + { + t.target.visit( payout_visitor( db, payout_amount ) ); + } + remaining -= payout_amount; + } + } + }; + + struct by_account; + + typedef multi_index_container< + splitter_object, + indexed_by< + hashed_unique< tag, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, member< splitter_object, account_id_type, &splitter_object::owner > > + > + > splitter_multi_index_type; + typedef generic_index splitter_index; + + + struct target_evalautor + { + typedef void result_type; + const database& db; + + target_evalautor( database& d ):db(d){} + + void operator()( const account_id_type& id )const { id(db); } + void operator()( const market_buyback& t )const + { + /// dereference these objects to verify they exist + t.asset_to_buy(db); + t.limit_price.base.asset_id(db); + } + + }; + + class splitter_create_evaluator : public evaluator + { + public: + typedef splitter_create_operation operation_type; + + void_result do_evaluate( const splitter_create_operation& o ) + { + o.owner(db()); // dereference to prove it exists + for( auto& t : o.targets ) + t.target.visit( target_evalautor(db()) ); + return void_result(); + } + + object_id_type do_apply( const splitter_create_operation& o ) + { + const auto& new_splitter_object = db().create( [&]( splitter_object& obj ){ + obj.owner = o.owner; + obj.targets = o.targets; + obj.min_payment = o.min_payment; + obj.max_payment = o.max_payment; + obj.payout_threshold = o.payout_threshold; + obj.balance.asset_id = o.min_payment.asset_id; + }); + return new_splitter_object.id; + } + }; + + class splitter_update_evaluator : public evaluator + { + public: + typedef splitter_update_operation operation_type; + + void_result do_evaluate( const splitter_update_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + FC_ASSERT( sp.balance.amount == 0 ); + FC_ASSERT( sp.owner == o.owner ); + + for( auto& t : o.targets ) + t.target.visit( target_evalautor(db()) ); + return void_result(); + } + void_result do_apply( const splitter_update_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + db().modify( sp, [&]( splitter_object& obj ){ + obj.targets = o.targets; + obj.owner = o.new_owner; + obj.min_payment = o.min_payment; + obj.max_payment = o.max_payment; + obj.payout_threshold = o.payout_threshold; + obj.balance.asset_id = o.min_payment.asset_id; + }); + return void_result(); + } + }; + + class splitter_pay_evaluator : public evaluator + { + public: + typedef splitter_pay_operation operation_type; + + void_result do_evaluate( const splitter_pay_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + FC_ASSERT( o.payment.asset_id == sp.min_payment.asset_id ); + FC_ASSERT( o.payment >= sp.min_payment ); + FC_ASSERT( o.payment.amount <= sp.max_payment ); + return void_result(); + } + void_result do_apply( const splitter_pay_operation& o ) + { + db().adjust_balance( o.paying_account, -o.payment ); + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + db().modify( sp, [&]( splitter_object& obj ){ + obj.balance += o.payment; + }); + + if( sp.balance.amount > sp.payout_threshold ) + sp.payout(db()); + + return void_result(); + } + }; + class splitter_payout_evaluator : public evaluator + { + public: + typedef splitter_payout_operation operation_type; + + void_result do_evaluate( const splitter_payout_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + FC_ASSERT( sp.owner == o.owner ); + FC_ASSERT( sp.balance.amount > 0 ); + return void_result(); + } + + void_result do_apply( const splitter_payout_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + sp.payout(db()); + return void_result(); + } + }; + class splitter_delete_evaluator : public evaluator + { + public: + typedef splitter_delete_operation operation_type; + + void_result do_evaluate( const splitter_delete_operation& o ) + { + const auto& sp = o.splitter_id(db()); // dereference to prove it exists + FC_ASSERT( sp.owner == o.owner ); + FC_ASSERT( sp.balance.amount == 0 ); + return void_result(); + } + void_result do_apply( const splitter_delete_operation& o ) + { + db().remove( o.splitter_id(db()) ); + return void_result(); + } + }; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::splitter_object, + (graphene::db::object), + (owner)(balance)(targets)(min_payment)(max_payment)(payout_threshold) + ) diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 6022b9fe..6deb8755 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include using namespace graphene::chain;