Skip to content

Commit

Permalink
Fix routing of fees to correct parties.
Browse files Browse the repository at this point in the history
  • Loading branch information
christophersanborn committed Apr 21, 2020
1 parent 329e5ac commit 9931176
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 20 deletions.
81 changes: 62 additions & 19 deletions libraries/chain/db_market.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -911,28 +911,58 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
return collateral_freed.valid();
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }

/***
* @brief fullfill a settle order in the specified amounts
*
* @details Called from database::match(), this coordinates exchange of debt asset X held in the
* settle order for collateral asset Y held in a call order, and routes fees. Note that we
* don't touch the call order directly, as match() handles this via a separate call to
* fill_call_order(). We are told exactly how much X and Y to exchange, based on details of
* order matching determined higher up the call chain. Thus it is possible that the settle
* order is not completely satisfied at the conclusion of this function.
*
* @param settle the force_settlement object
* @param pays the quantity of market-issued debt asset X which the settler will yield in this
* round (may be less than the full amount indicated in settle object)
* @param receives the quantity of collateral asset Y which the settler will receive in
* exchange for X
* @param fill_price the price at which the settle order will execute (not used - passed through
* to virtual operation)
* @param is_maker TRUE if the settle order is the maker, FALSE if it is the taker (passed
* through to virtual operation)
* @returns TRUE if the settle order was completely filled, FALSE if only partially filled
*/
bool database::fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives,
const price& fill_price, const bool is_maker )
{ try {
bool filled = false;

const account_object* settle_owner_ptr = nullptr;
// The owner of the settle order pays market fees to the issuer of the collateral asset after HF core-1780
// The owner of the settle order pays market fees to the issuer of the collateral asset.
// After HF core-1780, these fees are shared to the referral program, which is flagged to
// pay_market_fees by setting settle_owner_ptr non-null.
//
// TODO Check whether the HF check can be removed after the HF.
// Note: even if logically it can be removed, perhaps the removal will lead to a small performance
// loss. Needs testing.
if( head_block_time() >= HARDFORK_CORE_1780_TIME )
settle_owner_ptr = &settle.owner(*this);
// Compute and pay the market fees:
asset market_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives );

// Issuer of the settled smartcoin asset lays claim to a force-settlement fee (BSIP87), but
// note that fee is denominated in collateral asset, not the debt asset. Asset object of
// debt asset is passed to the pay function so it knows where to put the fee. Note that
// amount of collateral asset upon which fee is assessed is reduced by market_fees already
// paid to prevent the total fee exceeding total collateral.
asset force_settle_fees = pay_force_settle_fees( get(pays.asset_id), receives - market_fees );

// Issuer lays claim to both market fees and force-settlement fees (BSIP87) when a settle
// order is filled. Note that when computing force-settle fees, we reduce the 'receives'
// amount by the previous market fee so that the combined effect of the two fees will be
// multiplicative rather than additive. This prevents the net fee exceeding 100%.
const asset_object & recv_asset_obj = get(receives.asset_id);
asset issuer_fees = pay_market_fees( settle_owner_ptr, recv_asset_obj, receives );
issuer_fees += calculate_force_settle_fees( recv_asset_obj, receives - issuer_fees );
auto total_collateral_denominated_fees = market_fees + force_settle_fees;

// TODO: Do we need a something-for-nothing check here? Or does rounding guarantee fees
// strictly less than receives?

// If we don't consume entire settle order:
if( pays < settle.balance )
{
modify(settle, [&pays](force_settlement_object& s) {
Expand All @@ -941,15 +971,17 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a
} else {
filled = true;
}
adjust_balance(settle.owner, receives - issuer_fees);
// Give released collateral not already taken as fees to settle order owner:
adjust_balance(settle.owner, receives - total_collateral_denominated_fees);

assert( pays.asset_id != receives.asset_id );
push_applied_operation( fill_order_operation( settle.id, settle.owner, pays, receives, issuer_fees, fill_price, is_maker ) );
push_applied_operation( fill_order_operation( settle.id, settle.owner, pays, receives, total_collateral_denominated_fees, fill_price, is_maker ) );

if (filled)
remove(settle);

return filled;

} FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) }

/**
Expand Down Expand Up @@ -1300,19 +1332,30 @@ asset database::pay_market_fees(const account_object* seller, const asset_object
}

/***
* @brief Calculate force-settlement fee as per BSIP87
* @param recv_asset the collateral asset (passed in to avoid a lookup)
* @param receives the amount of collateral settler would expect to receive prior to this fee
* @brief Calculate force-settlement fee and give it to issuer of the settled asset
* @param collecting_asset the smart asset object which should receive the the fee
* @param collat_receives the amount of collateral the settler would expect to receive absent this fee
* (fee is computed as a percentage of this amount)
* @return asset representing the fee amount
* @return asset denoting the amount of fee collected
*/
asset database::calculate_force_settle_fees(const asset_object& recv_asset, const asset& receives) const
asset database::pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives)
{
if( !recv_asset.options.extensions.value.force_settle_fee_percent.valid()
|| *recv_asset.options.extensions.value.force_settle_fee_percent == 0 )
const asset_object& recv_asset = get(collat_receives.asset_id);
FC_ASSERT( collecting_asset.get_id() != collat_receives.asset_id );
if( !collecting_asset.options.extensions.value.force_settle_fee_percent.valid()
|| *collecting_asset.options.extensions.value.force_settle_fee_percent == 0 )
return recv_asset.amount(0);
auto value = detail::calculate_percent(receives.amount, *recv_asset.options.extensions.value.force_settle_fee_percent);
return recv_asset.amount(value);
auto value = detail::calculate_percent(collat_receives.amount, *collecting_asset.options.extensions.value.force_settle_fee_percent);
asset settle_fee = recv_asset.amount(value);
// Deposit fee in asset's dynamic data object:
if( value > 0)
{
const auto& asset_dyn_data = collecting_asset.dynamic_asset_data_id(*this);
modify( asset_dyn_data, [&settle_fee]( asset_dynamic_data_object& obj ){
obj.accumulated_fees += settle_fee.amount; // TODO: Wrong place to put fee
});
}
return settle_fee;
}

} }
2 changes: 1 addition & 1 deletion libraries/chain/include/graphene/chain/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ namespace graphene { namespace chain {

asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount);
asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives );
asset calculate_force_settle_fees(const asset_object& recv_asset, const asset& receives) const;
asset pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives);
///@}


Expand Down

0 comments on commit 9931176

Please sign in to comment.