Revert "Remove bond operations"

This reverts commit d12d9cdb0c.
This commit is contained in:
Vikram Rajkumar 2015-06-15 15:10:40 -04:00
parent d12d9cdb0c
commit 5f79b662dc
14 changed files with 744 additions and 0 deletions

View file

@ -18,6 +18,7 @@ add_library( graphene_chain
proposal_evaluator.cpp
short_order_evaluator.cpp
limit_order_evaluator.cpp
bond_evaluator.cpp
vesting_balance_evaluator.cpp
withdraw_permission_evaluator.cpp
worker_evaluator.cpp

View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/bond_evaluator.hpp>
#include <graphene/chain/bond_object.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
object_id_type bond_create_offer_evaluator::do_evaluate( const bond_create_offer_operation& op )
{
const auto& d = db();
const auto& creator_account = op.creator( d );
const auto& base_asset = op.collateral_rate.base.asset_id( d );
const auto& quote_asset = op.collateral_rate.quote.asset_id( d );
// TODO: Check asset authorizations and withdrawals
const auto& amount_asset = (op.amount.asset_id == op.collateral_rate.base.asset_id) ? base_asset : quote_asset;
FC_ASSERT( !base_asset.is_transfer_restricted() && !quote_asset.is_transfer_restricted() );
if( base_asset.options.whitelist_markets.size() )
FC_ASSERT( base_asset.options.whitelist_markets.find( quote_asset.id ) != base_asset.options.whitelist_markets.end() );
if( base_asset.options.blacklist_markets.size() )
FC_ASSERT( base_asset.options.blacklist_markets.find( quote_asset.id ) == base_asset.options.blacklist_markets.end() );
FC_ASSERT( d.get_balance( creator_account, amount_asset ) >= op.amount );
return object_id_type();
}
object_id_type bond_create_offer_evaluator::do_apply( const bond_create_offer_operation& op )
{
db().adjust_balance( op.creator, -op.amount );
db().adjust_core_in_orders( op.creator(db()), op.amount );
const auto& offer = db().create<bond_offer_object>( [&]( bond_offer_object& obj )
{
obj.offered_by_account = op.creator;
obj.offer_to_borrow = op.offer_to_borrow;
obj.amount = op.amount;
obj.min_match = op.min_match;
obj.collateral_rate = op.collateral_rate;
obj.min_loan_period_sec = op.min_loan_period_sec;
obj.loan_period_sec = op.loan_period_sec;
obj.interest_apr = op.interest_apr;
} );
return offer.id;
}
object_id_type bond_cancel_offer_evaluator::do_evaluate( const bond_cancel_offer_operation& op )
{
_offer = &op.offer_id(db());
FC_ASSERT( op.creator == _offer->offered_by_account );
FC_ASSERT( _offer->amount == op.refund );
return object_id_type();
}
object_id_type bond_cancel_offer_evaluator::do_apply( const bond_cancel_offer_operation& op )
{
assert( _offer != nullptr );
db().adjust_balance( op.creator, op.refund );
db().adjust_core_in_orders( op.creator(db()), -op.refund );
db().remove( *_offer );
return object_id_type();
}
object_id_type bond_accept_offer_evaluator::do_evaluate( const bond_accept_offer_operation& op )
{ try {
_offer = &op.offer_id(db());
if( _offer->offer_to_borrow )
FC_ASSERT( op.amount_borrowed.amount >= _offer->min_match );
else
FC_ASSERT( op.amount_collateral.amount >= _offer->min_match );
FC_ASSERT( (op.amount_borrowed / op.amount_collateral == _offer->collateral_rate) ||
(op.amount_collateral / op.amount_borrowed == _offer->collateral_rate) );
return object_id_type();
} FC_CAPTURE_AND_RETHROW((op)) }
object_id_type bond_accept_offer_evaluator::do_apply( const bond_accept_offer_operation& op )
{ try {
if( op.claimer == op.lender )
{
db().adjust_balance( op.lender, -op.amount_borrowed );
}
else // claimer == borrower
{
db().adjust_balance( op.borrower, -op.amount_collateral );
db().adjust_core_in_orders( op.borrower(db()), op.amount_collateral );
}
db().adjust_balance( op.borrower, op.amount_borrowed );
const auto& bond = db().create<bond_object>( [&]( bond_object& obj )
{
obj.borrowed = op.amount_borrowed;
obj.collateral = op.amount_collateral;
obj.borrower = op.borrower;
obj.lender = op.lender;
auto head_time = db().get_dynamic_global_properties().time;
obj.interest_apr = _offer->interest_apr;
obj.start_date = head_time;
obj.due_date = head_time + fc::seconds( _offer->loan_period_sec );
obj.earliest_payoff_date = head_time + fc::seconds( _offer->min_loan_period_sec );
} );
if( _offer->offer_to_borrow && op.amount_borrowed < _offer->amount )
{
db().modify( *_offer, [&]( bond_offer_object& offer ){
offer.amount -= op.amount_borrowed;
});
}
else if( !_offer->offer_to_borrow && op.amount_collateral < _offer->amount )
{
db().modify( *_offer, [&]( bond_offer_object& offer ){
offer.amount -= op.amount_collateral;
});
}
else
{
db().remove( *_offer );
}
return bond.id;
} FC_CAPTURE_AND_RETHROW((op)) }
object_id_type bond_claim_collateral_evaluator::do_evaluate( const bond_claim_collateral_operation& op )
{
_bond = &op.bond_id(db());
auto head_time = db().get_dynamic_global_properties().time;
FC_ASSERT( head_time > _bond->earliest_payoff_date );
FC_ASSERT( op.collateral_claimed <= _bond->collateral );
if( _bond->borrower == op.claimer )
{
auto elapsed_time = head_time - _bond->start_date;
auto elapsed_days = 1 + elapsed_time.to_seconds() / (60*60*24);
fc::uint128 tmp = _bond->borrowed.amount.value;
tmp *= elapsed_days;
tmp *= _bond->interest_apr;
tmp /= (365 * GRAPHENE_100_PERCENT);
FC_ASSERT( tmp < GRAPHENE_MAX_SHARE_SUPPLY );
_interest_due = asset(tmp.to_uint64(), _bond->borrowed.asset_id);
FC_ASSERT( _interest_due + _bond->borrowed <= op.payoff_amount );
auto total_debt = _interest_due + _bond->borrowed;
fc::uint128 max_claim = _bond->collateral.amount.value;
max_claim *= op.payoff_amount.amount.value;
max_claim /= total_debt.amount.value;
FC_ASSERT( op.collateral_claimed.amount.value == max_claim.to_uint64() );
}
else
{
FC_ASSERT( _bond->lender == op.claimer );
FC_ASSERT( head_time > _bond->due_date );
FC_ASSERT( _bond->collateral == op.collateral_claimed );
FC_ASSERT( op.payoff_amount == asset(0,_bond->borrowed.asset_id ) );
}
return object_id_type();
}
object_id_type bond_claim_collateral_evaluator::do_apply( const bond_claim_collateral_operation& op )
{
assert( _bond != nullptr );
const account_object& claimer = op.claimer(db());
db().adjust_core_in_orders( _bond->borrower(db()), -op.collateral_claimed );
if( op.payoff_amount.amount > 0 )
{
db().adjust_balance( claimer, -op.payoff_amount );
db().adjust_balance( op.lender, op.payoff_amount );
}
db().adjust_balance( claimer, op.collateral_claimed );
if( op.collateral_claimed == _bond->collateral )
db().remove(*_bond);
else
db().modify( *_bond, [&]( bond_object& bond ){
bond.borrowed -= op.payoff_amount + _interest_due;
bond.collateral -= op.collateral_claimed;
bond.start_date = db().get_dynamic_global_properties().time;
});
return object_id_type();
}
} } // graphene::chain

View file

@ -21,6 +21,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/block_summary_object.hpp>
#include <graphene/chain/bond_object.hpp>
#include <graphene/chain/delegate_object.hpp>
#include <graphene/chain/global_property_object.hpp>
#include <graphene/chain/key_object.hpp>
@ -36,6 +37,7 @@
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/asset_evaluator.hpp>
#include <graphene/chain/bond_evaluator.hpp>
#include <graphene/chain/custom_evaluator.hpp>
#include <graphene/chain/delegate_evaluator.hpp>
#include <graphene/chain/global_parameters_evaluator.hpp>
@ -87,6 +89,10 @@ void database::initialize_evaluators()
register_evaluator<global_parameters_update_evaluator>();
register_evaluator<witness_create_evaluator>();
register_evaluator<witness_withdraw_pay_evaluator>();
register_evaluator<bond_create_offer_evaluator>();
register_evaluator<bond_cancel_offer_evaluator>();
register_evaluator<bond_accept_offer_evaluator>();
register_evaluator<bond_claim_collateral_evaluator>();
register_evaluator<vesting_balance_create_evaluator>();
register_evaluator<vesting_balance_withdraw_evaluator>();
register_evaluator<withdraw_permission_create_evaluator>();
@ -112,6 +118,8 @@ void database::initialize_indexes()
add_index< primary_index<call_order_index > >();
add_index< primary_index<proposal_index > >();
add_index< primary_index<withdraw_permission_index > >();
add_index< primary_index<bond_index > >();
add_index< primary_index<bond_offer_index > >();
add_index< primary_index<simple_index<vesting_balance_object> > >();
add_index< primary_index<worker_index> >();

View file

@ -20,6 +20,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/bond_object.hpp>
#include <graphene/chain/limit_order_object.hpp>
#include <graphene/chain/short_order_object.hpp>
@ -32,6 +33,7 @@ namespace graphene { namespace chain {
calculate the USD->CORE price and convert all USD balances to CORE at that price and subtract CORE from total
- any fees accumulated by the issuer in the bitasset are forfeit / not redeemed
- cancel all open orders with bitasset in it
- any bonds with the bitasset as collateral get converted to CORE as collateral
- any bitassets that use this bitasset as collateral are immediately settled at their feed price
- convert all balances in bitasset to CORE and subtract from total
- any prediction markets with usd as the backing get converted to CORE as the backing
@ -109,6 +111,38 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
// settle all balances
asset total_mia_settled = mia.amount(0);
// convert collateral held in bonds
const auto& bond_idx = get_index_type<bond_index>().indices().get<by_collateral>();
auto bond_itr = bond_idx.find( bitasset.id );
while( bond_itr != bond_idx.end() )
{
if( bond_itr->collateral.asset_id == bitasset.id )
{
auto settled_amount = bond_itr->collateral * settlement_price;
total_mia_settled += bond_itr->collateral;
collateral_gathered -= settled_amount;
modify( *bond_itr, [&]( bond_object& obj ) {
obj.collateral = settled_amount;
});
}
else break;
}
// cancel all bond offers holding the bitasset and refund the offer
const auto& bond_offer_idx = get_index_type<bond_offer_index>().indices().get<by_asset>();
auto bond_offer_itr = bond_offer_idx.find( bitasset.id );
while( bond_offer_itr != bond_offer_idx.end() )
{
if( bond_offer_itr->amount.asset_id == bitasset.id )
{
adjust_balance( bond_offer_itr->offered_by_account, bond_offer_itr->amount );
auto old_itr = bond_offer_itr;
bond_offer_itr++;
remove( *old_itr );
}
else break;
}
const auto& index = get_index_type<account_balance_index>().indices().get<by_asset>();
auto range = index.equal_range(mia.get_id());
for( auto itr = range.first; itr != range.second; ++itr )

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <graphene/chain/evaluator.hpp>
namespace graphene { namespace chain {
class bond_create_offer_evaluator : public evaluator<bond_create_offer_evaluator>
{
public:
typedef bond_create_offer_operation operation_type;
object_id_type do_evaluate( const bond_create_offer_operation& op );
object_id_type do_apply( const bond_create_offer_operation& op );
};
class bond_cancel_offer_evaluator : public evaluator<bond_cancel_offer_evaluator>
{
public:
typedef bond_cancel_offer_operation operation_type;
object_id_type do_evaluate( const bond_cancel_offer_operation& op );
object_id_type do_apply( const bond_cancel_offer_operation& op );
const bond_offer_object* _offer = nullptr;
};
class bond_accept_offer_evaluator : public evaluator<bond_accept_offer_evaluator>
{
public:
typedef bond_accept_offer_operation operation_type;
object_id_type do_evaluate( const bond_accept_offer_operation& op );
object_id_type do_apply( const bond_accept_offer_operation& op );
const bond_offer_object* _offer = nullptr;
asset _fill_amount;
};
class bond_claim_collateral_evaluator : public evaluator<bond_claim_collateral_evaluator>
{
public:
typedef bond_claim_collateral_operation operation_type;
object_id_type do_evaluate( const bond_claim_collateral_operation& op );
object_id_type do_apply( const bond_claim_collateral_operation& op );
const bond_object* _bond = nullptr;
asset _interest_due;
};
} } // graphene::chain

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#pragma once
#include <graphene/chain/authority.hpp>
#include <graphene/chain/asset.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
/**
* @ingroup object
*/
class bond_object : public graphene::db::abstract_object<bond_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = bond_object_type;
asset_id_type collateral_type()const { return collateral.asset_id; }
account_id_type borrower;
account_id_type lender;
asset borrowed;
/** if collateral is the core asset, then voting rights belong to the borrower
* because the borrower is owner of the collateral until they default
*/
asset collateral;
uint16_t interest_apr = 0;
time_point_sec start_date;
/** after this date the lender can collect the collateral at will or let it float */
time_point_sec due_date;
/** the loan cannot be paid off before this date */
time_point_sec earliest_payoff_date;
};
/**
* @ingroup object
*/
class bond_offer_object : public graphene::db::abstract_object<bond_offer_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = bond_offer_object_type;
asset_id_type asset_type()const { return amount.asset_id; }
account_id_type offered_by_account;
bool offer_to_borrow = false; // Offer to borrow if true, and offer to lend otherwise
asset amount;
share_type min_match; ///< asset type same as ammount.asset_id
price collateral_rate;
uint32_t min_loan_period_sec = 0;
uint32_t loan_period_sec = 0;
uint16_t interest_apr = 0;
};
struct by_borrower;
struct by_lender;
struct by_offerer;
struct by_collateral; /// needed for blackswan resolution
struct by_asset; /// needed for blackswan resolution
typedef multi_index_container<
bond_object,
indexed_by<
hashed_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_borrower>, member<bond_object, account_id_type, &bond_object::borrower> >,
ordered_non_unique< tag<by_lender>, member<bond_object, account_id_type, &bond_object::lender> >,
hashed_non_unique< tag<by_collateral>, const_mem_fun<bond_object, asset_id_type, &bond_object::collateral_type> >
>
> bond_object_multi_index_type;
typedef generic_index<bond_object, bond_object_multi_index_type> bond_index;
/**
* Todo: consider adding index of tuple<collateral_type,loan_asset_type,interest_rate>
* Todo: consider adding index of tuple<collateral_type,loan_asset_type,period>
*/
typedef multi_index_container<
bond_offer_object,
indexed_by<
hashed_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_offerer>, member<bond_offer_object, account_id_type, &bond_offer_object::offered_by_account> >,
hashed_non_unique< tag<by_asset>, const_mem_fun<bond_offer_object, asset_id_type, &bond_offer_object::asset_type> >
>
> bond_offer_object_multi_index_type;
typedef generic_index<bond_offer_object, bond_offer_object_multi_index_type> bond_offer_index;
}} // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::bond_object, (graphene::db::object),
(borrower)(lender)(borrowed)(collateral)(interest_apr)(start_date)(due_date)(earliest_payoff_date) )
FC_REFLECT_DERIVED( graphene::chain::bond_offer_object, (graphene::db::object), (offered_by_account)(offer_to_borrow)(amount)(min_match)(collateral_rate)(min_loan_period_sec)(loan_period_sec)(interest_apr) )

View file

@ -1248,6 +1248,123 @@ namespace graphene { namespace chain {
}
};
/**
* @ingroup operations
*
* Bond offers are objects that exist on the blockchain and can be
* filled in full or in part by someone using the accept_bond_offer
* operation. When the offer is accepted a new bond_object is
* created that defines the terms of the loan.
*
* @return bond_offer_id
*/
struct bond_create_offer_operation
{
asset fee;
account_id_type creator;
bool offer_to_borrow = false; ///< Offer to borrow if true, and offer to lend otherwise
asset amount; ///< Amount to lend or secure depending on above
share_type min_match; ///< asset id same as amount.asset_id and sets the minimum match that will be accepted
price collateral_rate; ///< To derive amount of collateral or principle based on above
/** after this time the lender can let the loan float or collect the collateral at will */
uint32_t min_loan_period_sec = 0; ///< the earliest the loan may be paid off
uint32_t loan_period_sec = 0;
uint16_t interest_apr = 0; ///< MAX_INTEREST_APR == 100% and is max value
account_id_type fee_payer()const { return creator; }
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
void validate()const;
share_type calculate_fee( const fee_schedule_type& k )const;
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const
{
acc.adjust( fee_payer(), -fee );
acc.adjust( creator, -amount );
}
};
/**
* @ingroup operations
* Subtracts refund from bond_offer.amount and frees bond_offer if refund == bond_offer.amount
*/
struct bond_cancel_offer_operation
{
asset fee;
account_id_type creator;
bond_offer_id_type offer_id;
asset refund;
account_id_type fee_payer()const { return creator; }
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
void validate()const;
share_type calculate_fee( const fee_schedule_type& k )const;
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const
{
acc.adjust( fee_payer(), -fee );
acc.adjust( creator, refund );
}
};
/**
* @ingroup operations
* @return new bond_id
*/
struct bond_accept_offer_operation
{
asset fee;
account_id_type claimer;
account_id_type lender;
account_id_type borrower; ///< included in case of offer to borrow, because borrower will receive funds
bond_offer_id_type offer_id;
asset amount_borrowed; ///< should equal amount_collateral * offer_id->collateral_rate
asset amount_collateral; ///< should equal amount_borrowed * offer_id->collateral_rate
account_id_type fee_payer()const { return claimer; }
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
void validate()const;
share_type calculate_fee( const fee_schedule_type& k )const;
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const
{
acc.adjust( fee_payer(), -fee );
if( claimer == lender )
acc.adjust( claimer, -amount_borrowed );
else // claimer == borrower
acc.adjust( claimer, -amount_collateral );
acc.adjust( borrower, amount_borrowed );
}
};
/**
* @ingroup operations
* After the loan period the lender can claim
* the collateral, prior to the loan period expiring
* the borrower can claim it by paying off the loan
*/
struct bond_claim_collateral_operation
{
asset fee;
account_id_type claimer; ///< must be bond_id->lender or bond_id->borrower
account_id_type lender; ///< must be bond_id->lender
bond_id_type bond_id;
asset payoff_amount;
/** the borrower can claim a percentage of the collateral propotional to the
* percentage of the debt+interest that was paid off
*/
asset collateral_claimed;
account_id_type fee_payer()const { return claimer; }
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
void validate()const;
share_type calculate_fee( const fee_schedule_type& k )const;
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const
{
acc.adjust( fee_payer(), -fee );
acc.adjust( claimer, -payoff_amount );
acc.adjust( claimer, collateral_claimed );
acc.adjust( lender, payoff_amount );
}
};
/**
* @brief Create a vesting balance.
* @ingroup operations
@ -1427,6 +1544,10 @@ namespace graphene { namespace chain {
global_parameters_update_operation,
vesting_balance_create_operation,
vesting_balance_withdraw_operation,
bond_create_offer_operation,
bond_cancel_offer_operation,
bond_accept_offer_operation,
bond_claim_collateral_operation,
worker_create_operation,
custom_operation
> operation;
@ -1655,6 +1776,11 @@ FC_REFLECT( graphene::chain::withdraw_permission_claim_operation, (fee)(withdraw
FC_REFLECT( graphene::chain::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account)
(withdrawal_permission) )
FC_REFLECT( graphene::chain::bond_create_offer_operation, (fee)(creator)(offer_to_borrow)(amount)(min_match)(collateral_rate)(min_loan_period_sec)(loan_period_sec)(interest_apr) )
FC_REFLECT( graphene::chain::bond_cancel_offer_operation, (fee)(creator)(offer_id)(refund) )
FC_REFLECT( graphene::chain::bond_accept_offer_operation, (fee)(claimer)(lender)(borrower)(offer_id)(amount_borrowed)(amount_collateral) )
FC_REFLECT( graphene::chain::bond_claim_collateral_operation, (fee)(claimer)(lender)(bond_id)(payoff_amount)(collateral_claimed) )
FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(vesting_seconds) )
FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) )

View file

@ -114,6 +114,8 @@ namespace graphene { namespace chain {
proposal_object_type,
operation_history_object_type,
withdraw_permission_object_type,
bond_offer_object_type,
bond_object_type,
vesting_balance_object_type,
worker_object_type,
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
@ -158,6 +160,8 @@ namespace graphene { namespace chain {
class proposal_object;
class operation_history_object;
class withdraw_permission_object;
class bond_object;
class bond_offer_object;
class vesting_balance_object;
class witness_schedule_object;
class worker_object;
@ -175,6 +179,8 @@ namespace graphene { namespace chain {
typedef object_id< protocol_ids, proposal_object_type, proposal_object> proposal_id_type;
typedef object_id< protocol_ids, operation_history_object_type, operation_history_object> operation_history_id_type;
typedef object_id< protocol_ids, withdraw_permission_object_type,withdraw_permission_object> withdraw_permission_id_type;
typedef object_id< protocol_ids, bond_offer_object_type, bond_offer_object> bond_offer_id_type;
typedef object_id< protocol_ids, bond_object_type, bond_object> bond_id_type;
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;
@ -373,6 +379,10 @@ namespace graphene { namespace chain {
uint32_t membership_annual_fee; ///< the annual cost of a membership subscription
uint32_t membership_lifetime_fee; ///< the cost to upgrade to a lifetime member
uint32_t withdraw_permission_update_fee; ///< the cost to create/update a withdraw permission
uint32_t create_bond_offer_fee;
uint32_t cancel_bond_offer_fee;
uint32_t accept_bond_offer_fee;
uint32_t claim_bond_collateral_fee;
uint32_t vesting_balance_create_fee;
uint32_t vesting_balance_withdraw_fee;
uint32_t global_settle_fee;
@ -493,6 +503,8 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(proposal_object_type)
(operation_history_object_type)
(withdraw_permission_object_type)
(bond_offer_object_type)
(bond_object_type)
(vesting_balance_object_type)
(worker_object_type)
(OBJECT_TYPE_COUNT)
@ -547,6 +559,10 @@ FC_REFLECT( graphene::chain::fee_schedule_type,
(membership_annual_fee)
(membership_lifetime_fee)
(withdraw_permission_update_fee)
(create_bond_offer_fee)
(cancel_bond_offer_fee)
(accept_bond_offer_fee)
(claim_bond_collateral_fee)
(vesting_balance_create_fee)
(vesting_balance_withdraw_fee)
(global_settle_fee)
@ -593,6 +609,8 @@ FC_REFLECT_TYPENAME( graphene::chain::custom_id_type )
FC_REFLECT_TYPENAME( graphene::chain::proposal_id_type )
FC_REFLECT_TYPENAME( graphene::chain::operation_history_id_type )
FC_REFLECT_TYPENAME( graphene::chain::withdraw_permission_id_type )
FC_REFLECT_TYPENAME( graphene::chain::bond_offer_id_type )
FC_REFLECT_TYPENAME( graphene::chain::bond_id_type )
FC_REFLECT_TYPENAME( graphene::chain::vesting_balance_id_type )
FC_REFLECT_TYPENAME( graphene::chain::worker_id_type )
FC_REFLECT_TYPENAME( graphene::chain::relative_key_id_type )

View file

@ -804,6 +804,77 @@ share_type custom_operation::calculate_fee( const fee_schedule_type& k )const
return (data.size() * k.data_fee)/1024;
}
void bond_create_offer_operation::get_required_auth( flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>& )const
{
active_auth_set.insert( creator );
}
void bond_create_offer_operation::validate()const
{ try {
FC_ASSERT( fee.amount >= 0 );
FC_ASSERT( amount.amount > 0 );
collateral_rate.validate();
FC_ASSERT( (amount * collateral_rate).amount > 0 );
FC_ASSERT( min_loan_period_sec > 0 );
FC_ASSERT( loan_period_sec >= min_loan_period_sec );
FC_ASSERT( interest_apr <= GRAPHENE_MAX_INTEREST_APR );
} FC_CAPTURE_AND_RETHROW((*this)) }
share_type bond_create_offer_operation::calculate_fee( const fee_schedule_type& schedule )const
{
return schedule.create_bond_offer_fee;
}
void bond_cancel_offer_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const
{
active_auth_set.insert( creator );
}
void bond_cancel_offer_operation::validate()const
{
FC_ASSERT( fee.amount > 0 );
FC_ASSERT( refund.amount > 0 );
}
share_type bond_cancel_offer_operation::calculate_fee( const fee_schedule_type& k )const
{
return k.cancel_bond_offer_fee;
}
void bond_accept_offer_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const
{
active_auth_set.insert( claimer );
}
void bond_accept_offer_operation::validate()const
{
FC_ASSERT( fee.amount > 0 );
(amount_collateral / amount_borrowed).validate();
FC_ASSERT( claimer == borrower || claimer == lender );
FC_ASSERT( borrower != lender );
}
share_type bond_accept_offer_operation::calculate_fee( const fee_schedule_type& k )const
{
return k.accept_bond_offer_fee;
}
void bond_claim_collateral_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const
{
active_auth_set.insert( claimer );
}
void bond_claim_collateral_operation::validate()const
{
FC_ASSERT( fee.amount > 0 );
FC_ASSERT(payoff_amount.amount >= 0 );
FC_ASSERT(collateral_claimed.amount >= 0 );
FC_ASSERT( payoff_amount.asset_id != collateral_claimed.asset_id );
}
share_type bond_claim_collateral_operation::calculate_fee( const fee_schedule_type& k )const
{
return k.claim_bond_collateral_fee;
}
void worker_create_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&) const
{
active_auth_set.insert(owner);

View file

@ -232,6 +232,18 @@ struct operation_get_impacted_accounts
_impacted.insert( account_id_type() );
}
void operator()( const bond_create_offer_operation& o )const { }
void operator()( const bond_cancel_offer_operation& o )const { }
void operator()( const bond_accept_offer_operation& o )const {
_impacted.insert( o.borrower );
_impacted.insert( o.lender );
}
void operator()( const bond_claim_collateral_operation& o )const
{
_impacted.insert( o.lender );
_impacted.insert( o.claimer );
}
void operator()( const vesting_balance_create_operation& o )const
{
_impacted.insert( o.creator );

View file

@ -26,6 +26,7 @@
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/bond_object.hpp>
using namespace fc;
using namespace graphene::chain;
@ -86,6 +87,10 @@ object* create_object( const variant& v )
return create_object_of_type< operation_history_object >( v );
case withdraw_permission_object_type:
return create_object_of_type< withdraw_permission_object >( v );
case bond_offer_object_type:
return create_object_of_type< bond_offer_object >( v );
case bond_object_type:
return create_object_of_type< bond_object >( v );
default:
;
}

View file

@ -16,6 +16,7 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <graphene/chain/operations.hpp>
#include <graphene/chain/bond_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/proposal_object.hpp>

View file

@ -24,6 +24,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/bond_object.hpp>
#include <graphene/chain/delegate_object.hpp>
#include <graphene/chain/limit_order_object.hpp>
#include <graphene/chain/short_order_object.hpp>
@ -150,6 +151,12 @@ void database_fixture::verify_asset_supplies( )const
{
total_balances[asset_id_type()] += witness_obj.accumulated_income;
}
for( const bond_offer_object& bond_offer : db.get_index_type<bond_offer_index>().indices() )
{
total_balances[ bond_offer.amount.asset_id ] += bond_offer.amount.amount;
if( bond_offer.amount.asset_id == asset_id_type() )
core_in_orders += bond_offer.amount.amount;
}
for( const vesting_balance_object& vbo : db.get_index_type< simple_index<vesting_balance_object> >() )
total_balances[ vbo.balance.asset_id ] += vbo.balance.amount;

View file

@ -2080,6 +2080,7 @@ BOOST_AUTO_TEST_CASE( margin_call_black_swan )
* 2) Short Orders for BitAsset backed by BitUSD
* 3) Call Orders for BitAsset backed by BitUSD
* 4) Issuer Fees
* 5) Bond Market Collateral
*
* This test should fail until the black swan handling code can
* perform a recursive blackswan for any other BitAssets that use
@ -2137,6 +2138,69 @@ BOOST_AUTO_TEST_CASE( unimp_transfer_cashback_test )
}
}
BOOST_AUTO_TEST_CASE( bond_create_offer_test )
{ try {
bond_create_offer_operation op;
op.fee = asset( 0, 0 );
op.creator = account_id_type();
op.amount = asset( 1, 0 );
op.collateral_rate = price( asset( 1, 0 ), asset( 1, 1 ) );
op.min_loan_period_sec = 1;
op.loan_period_sec = 1;
// Fee must be non-negative
REQUIRE_OP_VALIDATION_SUCCESS( op, fee, asset( 1, 0 ) );
REQUIRE_OP_VALIDATION_SUCCESS( op, fee, asset( 0, 0 ) );
REQUIRE_OP_VALIDATION_FAILURE( op, fee, asset( -1, 0 ) );
// Amount must be positive
REQUIRE_OP_VALIDATION_SUCCESS( op, amount, asset( 1, 0 ) );
REQUIRE_OP_VALIDATION_FAILURE( op, amount, asset( 0, 0 ) );
REQUIRE_OP_VALIDATION_FAILURE( op, amount, asset( -1, 0 ) );
// Collateral rate must be valid
REQUIRE_OP_VALIDATION_SUCCESS( op, collateral_rate, price( asset( 1, 0 ), asset( 1, 1 ) ) );
REQUIRE_OP_VALIDATION_FAILURE( op, collateral_rate, price( asset( 0, 0 ), asset( 1, 1 ) ) );
REQUIRE_OP_VALIDATION_FAILURE( op, collateral_rate, price( asset( 1, 0 ), asset( 0, 1 ) ) );
REQUIRE_OP_VALIDATION_FAILURE( op, collateral_rate, price( asset( 1, 0 ), asset( 1, 0 ) ) );
// Min loan period must be at least 1 sec
REQUIRE_OP_VALIDATION_SUCCESS( op, min_loan_period_sec, 1 );
REQUIRE_OP_VALIDATION_FAILURE( op, min_loan_period_sec, 0 );
// Loan period must be greater than min load period
REQUIRE_OP_VALIDATION_SUCCESS( op, loan_period_sec, op.min_loan_period_sec + 1 );
REQUIRE_OP_VALIDATION_FAILURE( op, loan_period_sec, 0 );
// Interest APR cannot be greater than max
REQUIRE_OP_VALIDATION_FAILURE( op, interest_apr, GRAPHENE_MAX_INTEREST_APR + 1 );
REQUIRE_OP_VALIDATION_SUCCESS( op, interest_apr, GRAPHENE_MAX_INTEREST_APR );
REQUIRE_OP_VALIDATION_SUCCESS( op, interest_apr, 0 );
// Setup world state we will need to test actual evaluation
INVOKE( create_uia );
const auto& test_asset = get_asset( "TEST" );
const auto& nathan_account = create_account( "nathan" );
transfer( account_id_type()( db ), nathan_account, asset( 1, 0 ) );
op.creator = nathan_account.get_id();
op.collateral_rate.quote.asset_id = test_asset.get_id();
trx.operations.emplace_back( op );
// Insufficient funds in creator account
REQUIRE_THROW_WITH_VALUE( op, creator, account_id_type( 1 ) );
// Insufficient principle
REQUIRE_THROW_WITH_VALUE( op, amount, asset( 2, 0 ) );
// Insufficient collateral
op.offer_to_borrow = true;
REQUIRE_THROW_WITH_VALUE( op, amount, asset( 1, test_asset.get_id() ) );
// This op should be fully valid
REQUIRE_OP_EVALUATION_SUCCESS( op, offer_to_borrow, false );
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_CASE( vesting_balance_create_test )
{ try {
INVOKE( create_uia );