implement payment splitter proposal
This commit is contained in:
parent
e4c29cbe78
commit
c6b848ef18
6 changed files with 399 additions and 2 deletions
|
|
@ -44,6 +44,7 @@
|
|||
#include <graphene/chain/witness_evaluator.hpp>
|
||||
#include <graphene/chain/worker_evaluator.hpp>
|
||||
#include <graphene/chain/balance_evaluator.hpp>
|
||||
#include <graphene/chain/splitter_evaluator.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
||||
|
|
@ -150,6 +151,11 @@ void database::initialize_evaluators()
|
|||
register_evaluator<withdraw_permission_delete_evaluator>();
|
||||
register_evaluator<worker_create_evaluator>();
|
||||
register_evaluator<balance_claim_evaluator>();
|
||||
register_evaluator<splitter_create_evaluator>();
|
||||
register_evaluator<splitter_update_evaluator>();
|
||||
register_evaluator<splitter_pay_evaluator>();
|
||||
register_evaluator<splitter_payout_evaluator>();
|
||||
register_evaluator<splitter_delete_evaluator>();
|
||||
}
|
||||
|
||||
void database::initialize_indexes()
|
||||
|
|
@ -177,6 +183,7 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<vesting_balance_index> >();
|
||||
add_index< primary_index<worker_index> >();
|
||||
add_index< primary_index<balance_index> >();
|
||||
add_index< primary_index<splitter_index> >();
|
||||
|
||||
//Implementation object indexes
|
||||
add_index< primary_index<transaction_index > >();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <graphene/chain/protocol/withdraw_permission.hpp>
|
||||
#include <graphene/chain/protocol/witness.hpp>
|
||||
#include <graphene/chain/protocol/worker.hpp>
|
||||
#include <graphene/chain/protocol/splitter.hpp>
|
||||
|
||||
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
|
||||
|
|
|
|||
147
libraries/chain/include/graphene/chain/protocol/splitter.hpp
Normal file
147
libraries/chain/include/graphene/chain/protocol/splitter.hpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* Copyright (c) 2015, Cryptonomex, Inc. */
|
||||
#pragma once
|
||||
#include <graphene/chain/protocol/base.hpp>
|
||||
|
||||
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<account_id_type,market_buyback> 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<payment_target> 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<payment_target> 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) );
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
232
libraries/chain/include/graphene/chain/splitter_evaluator.hpp
Normal file
232
libraries/chain/include/graphene/chain/splitter_evaluator.hpp
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/* Copyright (c) 2015, Cryptonomex, Inc. */
|
||||
#pragma once
|
||||
#include <graphene/chain/protocol/operations.hpp>
|
||||
#include <graphene/chain/evaluator.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
class splitter_object : public abstract_object<splitter_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<payment_target> 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>([&](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<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_non_unique< tag<by_account>, member< splitter_object, account_id_type, &splitter_object::owner > >
|
||||
>
|
||||
> splitter_multi_index_type;
|
||||
typedef generic_index<splitter_object, splitter_multi_index_type> 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<splitter_create_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>( [&]( 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<splitter_update_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<splitter_pay_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<splitter_payout_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<splitter_delete_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)
|
||||
)
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
#include <graphene/chain/market_evaluator.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/balance_object.hpp>
|
||||
#include <graphene/chain/splitter_evaluator.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
|
|
|||
Loading…
Reference in a new issue