/** * Process all fees and update the asset records. */ void transaction_evaluation_state::post_evaluate() { try { for( const auto& item : withdraws ) { auto asset_rec = _current_state->get_asset_record( item.first ); if( !asset_rec.valid() ) FC_CAPTURE_AND_THROW( unknown_asset_id, (item) ); if( asset_rec->id > 0 && asset_rec->is_market_issued() && asset_rec->transaction_fee > 0 ) { sub_balance( address(), asset(asset_rec->transaction_fee, asset_rec->id) ); } } balance[0]; // make sure we have something for this. for( const auto& fee : balance ) { if( fee.second < 0 ) FC_CAPTURE_AND_THROW( negative_fee, (fee) ); // if the fee is already in XTS or the fee balance is zero, move along... if( fee.first == 0 || fee.second == 0 ) continue; auto asset_record = _current_state->get_asset_record( fee.first ); if( !asset_record.valid() ) FC_CAPTURE_AND_THROW( unknown_asset_id, (fee.first) ); if( !asset_record->is_market_issued() ) continue; // lowest ask is someone with XTS offered at a price of USD / XTS, fee.first // is an amount of USD which can be converted to price*USD XTS provided we // send lowest_ask.index.owner the USD oprice median_price = _current_state->get_median_delegate_price( fee.first, asset_id_type( 0 ) ); if( median_price ) { // fees paid in something other than XTS are discounted 50% alt_fees_paid += asset( (fee.second*2)/3, fee.first ) * *median_price; } } for( const auto& fee : balance ) { if( fee.second < 0 ) FC_CAPTURE_AND_THROW( negative_fee, (fee) ); if( fee.second > 0 ) // if a fee was paid... { auto asset_record = _current_state->get_asset_record( fee.first ); if( !asset_record ) FC_CAPTURE_AND_THROW( unknown_asset_id, (fee.first) ); asset_record->collected_fees += fee.second; _current_state->store_asset_record( *asset_record ); } } } FC_RETHROW_EXCEPTIONS( warn, "" ) }
/** * Process all fees and update the asset records. */ void transaction_evaluation_state::post_evaluate() { try { // Should this be here? We may not have fees in XTS now... balance[0]; // make sure we have something for this. for( const auto& fee : balance ) { if( fee.second < 0 ) FC_CAPTURE_AND_THROW( negative_fee, (fee) ); // if the fee is already in XTS or the fee balance is zero, move along... if( fee.first == 0 || fee.second == 0 ) continue; auto asset_record = _current_state->get_asset_record( fee.first ); if( !asset_record.valid() ) FC_CAPTURE_AND_THROW( unknown_asset_id, (fee.first) ); if( !asset_record->is_market_issued() ) continue; // lowest ask is someone with XTS offered at a price of USD / XTS, fee.first // is an amount of USD which can be converted to price*USD XTS provided we // send lowest_ask.index.owner the USD oprice median_price = _current_state->get_median_delegate_price( fee.first ); if( median_price ) { // fees paid in something other than XTS are discounted 50% alt_fees_paid += asset( (fee.second*2)/3, fee.first ) * *median_price; } } for( const auto& fee : balance ) { if( fee.second < 0 ) FC_CAPTURE_AND_THROW( negative_fee, (fee) ); if( fee.second > 0 ) // if a fee was paid... { auto asset_record = _current_state->get_asset_record( fee.first ); if( !asset_record ) FC_CAPTURE_AND_THROW( unknown_asset_id, (fee.first) ); asset_record->collected_fees += fee.second; _current_state->store_asset_record( *asset_record ); } } for( const auto& required_deposit : required_deposits ) { auto provided_itr = provided_deposits.find( required_deposit.first ); if( provided_itr->second < required_deposit.second ) FC_CAPTURE_AND_THROW( missing_deposit, (required_deposit) ); } } FC_RETHROW_EXCEPTIONS( warn, "" ) }
void withdraw_operation::evaluate( transaction_evaluation_state& eval_state )const { try { if( this->amount <= 0 ) FC_CAPTURE_AND_THROW( negative_withdraw, (amount) ); obalance_record current_balance_record = eval_state.pending_state()->get_balance_record( this->balance_id ); if( !current_balance_record.valid() ) FC_CAPTURE_AND_THROW( unknown_balance_record, (balance_id) ); if( this->amount > current_balance_record->get_spendable_balance( eval_state.pending_state()->now() ).amount ) FC_CAPTURE_AND_THROW( insufficient_funds, (current_balance_record)(amount) ); auto asset_rec = eval_state.pending_state()->get_asset_record( current_balance_record->condition.asset_id ); FC_ASSERT( asset_rec.valid() ); const bool authority_is_retracting = asset_rec->flag_is_active( asset_record::retractable_balances ) && eval_state.verify_authority( asset_rec->authority ); if( !authority_is_retracting ) { FC_ASSERT( !asset_rec->flag_is_active( asset_record::halted_withdrawals ) ); switch( (withdraw_condition_types)current_balance_record->condition.type ) { case withdraw_signature_type: { const withdraw_with_signature condition = current_balance_record->condition.as<withdraw_with_signature>(); const address owner = condition.owner; if( !eval_state.check_signature( owner ) ) FC_CAPTURE_AND_THROW( missing_signature, (owner) ); break; } case withdraw_vesting_type: { const withdraw_vesting condition = current_balance_record->condition.as<withdraw_vesting>(); const address owner = condition.owner; if( !eval_state.check_signature( owner ) ) FC_CAPTURE_AND_THROW( missing_signature, (owner) ); break; } case withdraw_multisig_type: { auto multisig = current_balance_record->condition.as<withdraw_with_multisig>(); uint32_t valid_signatures = 0; for( const auto& sig : multisig.owners ) valid_signatures += eval_state.check_signature( sig ); if( valid_signatures < multisig.required ) FC_CAPTURE_AND_THROW( missing_signature, (valid_signatures)(multisig) ); break; } default: FC_CAPTURE_AND_THROW( invalid_withdraw_condition, (current_balance_record->condition) ); } } // update delegate vote on withdrawn account.. if( current_balance_record->condition.asset_id == 0 && current_balance_record->condition.slate_id ) eval_state.adjust_vote( current_balance_record->condition.slate_id, -this->amount ); if( asset_rec->is_market_issued() ) { auto yield = current_balance_record->calculate_yield( eval_state.pending_state()->now(), current_balance_record->balance, asset_rec->collected_fees, asset_rec->current_supply ); if( yield.amount > 0 ) { asset_rec->collected_fees -= yield.amount; current_balance_record->balance += yield.amount; current_balance_record->deposit_date = eval_state.pending_state()->now(); eval_state.yield_claimed[ current_balance_record->asset_id() ] += yield.amount; eval_state.pending_state()->store_asset_record( *asset_rec ); } } current_balance_record->balance -= this->amount; current_balance_record->last_update = eval_state.pending_state()->now(); eval_state.pending_state()->store_balance_record( *current_balance_record ); if( asset_rec->withdrawal_fee != 0 && !eval_state.verify_authority( asset_rec->authority ) ) eval_state.min_fees[ asset_rec->id ] = std::max( asset_rec->withdrawal_fee, eval_state.min_fees[ asset_rec->id ] ); eval_state.add_balance( asset( this->amount, current_balance_record->condition.asset_id ) ); } FC_CAPTURE_AND_RETHROW( (*this) ) }
void update_balance_vote_operation::evaluate( transaction_evaluation_state& eval_state )const { try { auto current_balance_record = eval_state.pending_state()->get_balance_record( this->balance_id ); FC_ASSERT( current_balance_record.valid(), "No such balance!" ); FC_ASSERT( current_balance_record->condition.asset_id == 0, "Only BTS balances can have restricted owners." ); FC_ASSERT( current_balance_record->condition.type == withdraw_signature_type, "Restricted owners not enabled for anything but basic balances" ); auto last_update_secs = current_balance_record->last_update.sec_since_epoch(); ilog("last_update_secs is: ${secs}", ("secs", last_update_secs) ); auto balance = current_balance_record->balance; auto fee = BTS_BLOCKCHAIN_PRECISION / 2; FC_ASSERT( balance > fee ); auto asset_rec = eval_state.pending_state()->get_asset_record( current_balance_record->condition.asset_id ); if( asset_rec->is_market_issued() ) FC_ASSERT( current_balance_record->condition.slate_id == 0 ); if( current_balance_record->condition.slate_id ) { eval_state.adjust_vote( current_balance_record->condition.slate_id, -balance ); } current_balance_record->balance -= balance; current_balance_record->last_update = eval_state.pending_state()->now(); ilog("I'm storing a balance record whose last update is: ${secs}", ("secs", current_balance_record->last_update) ); eval_state.pending_state()->store_balance_record( *current_balance_record ); auto new_restricted_owner = current_balance_record->restricted_owner; auto new_slate = current_balance_record->condition.slate_id; if( this->new_restricted_owner.valid() && (this->new_restricted_owner != new_restricted_owner) ) { ilog("@n new restricted owner specified and its not the existing one"); for( const auto& owner : current_balance_record->owners() ) //eventually maybe multisig can delegate vote { if( !eval_state.check_signature( owner ) ) FC_CAPTURE_AND_THROW( missing_signature, (owner) ); } new_restricted_owner = this->new_restricted_owner; new_slate = this->new_slate; } else // NOT this->new_restricted_owner.valid() || (this->new_restricted_owner == new_restricted_owner) { auto restricted_owner = current_balance_record->restricted_owner; /* FC_ASSERT( restricted_owner.valid(), "Didn't specify a new restricted owner, but one currently exists." ); */ ilog( "@n now: ${secs}", ("secs", eval_state.pending_state()->now().sec_since_epoch()) ); ilog( "@n last update: ${secs}", ("secs", last_update_secs ) ); FC_ASSERT( eval_state.pending_state()->now().sec_since_epoch() - last_update_secs >= BTS_BLOCKCHAIN_VOTE_UPDATE_PERIOD_SEC, "You cannot update your vote this frequently with only the voting key!" ); if( NOT eval_state.check_signature( *restricted_owner ) ) { const auto& owners = current_balance_record->owners(); for( const auto& owner : owners ) //eventually maybe multisig can delegate vote { if( NOT eval_state.check_signature( owner ) ) FC_CAPTURE_AND_THROW( missing_signature, (owner) ); } } new_slate = this->new_slate; } const auto owner = current_balance_record->owner(); FC_ASSERT( owner.valid() ); withdraw_condition new_condition( withdraw_with_signature( *owner ), 0, new_slate ); balance_record newer_balance_record( new_condition ); auto new_balance_record = eval_state.pending_state()->get_balance_record( newer_balance_record.id() ); if( !new_balance_record.valid() ) new_balance_record = current_balance_record; new_balance_record->condition = new_condition; if( new_balance_record->balance == 0 ) { new_balance_record->deposit_date = eval_state.pending_state()->now(); } else { fc::uint128 old_sec_since_epoch( current_balance_record->deposit_date.sec_since_epoch() ); fc::uint128 new_sec_since_epoch( eval_state.pending_state()->now().sec_since_epoch() ); fc::uint128 avg = (old_sec_since_epoch * new_balance_record->balance) + (new_sec_since_epoch * balance); avg /= (new_balance_record->balance + balance); new_balance_record->deposit_date = time_point_sec( avg.to_integer() ); } new_balance_record->last_update = eval_state.pending_state()->now(); new_balance_record->balance += (balance - fee); new_balance_record->restricted_owner = new_restricted_owner; eval_state.add_balance( asset(fee, 0) ); // update delegate vote on deposited account.. if( new_balance_record->condition.slate_id ) eval_state.adjust_vote( new_balance_record->condition.slate_id, (balance-fee) ); ilog("I'm storing a balance record whose last update is: ${secs}", ("secs", new_balance_record->last_update) ); eval_state.pending_state()->store_balance_record( *new_balance_record ); } FC_CAPTURE_AND_RETHROW( (*this) ) }
void execute( asset_id_type quote_id, asset_id_type base_id, const fc::time_point_sec& timestamp ) { try { _quote_id = quote_id; _base_id = base_id; auto quote_asset = _pending_state->get_asset_record( _quote_id ); // DISABLE MARKET ISSUED ASSETS if( quote_asset->is_market_issued() ) return; // don't execute anything. // the order book is soreted from low to high price, so to get the last item (highest bid), we need to go to the first item in the // next market class and then back up one auto next_pair = base_id+1 == quote_id ? price( 0, quote_id+1, 0) : price( 0, quote_id, base_id+1 ); _bid_itr = _db_impl._bid_db.lower_bound( market_index_key( next_pair ) ); _ask_itr = _db_impl._ask_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) ); _short_itr = _db_impl._short_db.lower_bound( market_index_key( next_pair ) ); _collateral_itr = _db_impl._collateral_db.lower_bound( market_index_key( next_pair ) ); if( !_ask_itr.valid() ) { wlog( "ask iter invalid..." ); _ask_itr = _db_impl._ask_db.begin(); } if( _short_itr.valid() ) --_short_itr; else _short_itr = _db_impl._short_db.last(); if( _bid_itr.valid() ) --_bid_itr; else _bid_itr = _db_impl._bid_db.last(); if( _collateral_itr.valid() ) --_collateral_itr; else _collateral_itr = _db_impl._collateral_db.last(); asset consumed_bid_depth(0,base_id); asset consumed_ask_depth(0,base_id); asset usd_fees_collected(0,quote_id); asset trading_volume(0, base_id); omarket_status market_stat = _pending_state->get_market_status( _quote_id, _base_id ); if( !market_stat.valid() ) { if( quote_asset->is_market_issued() ) FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) ); FC_ASSERT( market_stat.valid() ); } while( get_next_bid() && get_next_ask() ) { idump( (_current_bid)(_current_ask) ); price ask_price = _current_ask->get_price(); // this works for bids, asks, and shorts.... but in the case of a cover // the current ask can go lower than the call price in order to match // the bid.... if( _current_ask->type == cover_order ) { ask_price = std::min( _current_bid->get_price(), _current_ask->get_highest_cover_price() ); } if( _current_bid->get_price() < ask_price ) break; if( quote_asset->is_market_issued() ) { if( !market_stat || market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 || market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ) FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) ); } auto quantity = std::min( _current_bid->get_quantity(), _current_ask->get_quantity() ); auto usd_paid_by_bid = quantity * _current_bid->get_price(); auto usd_received_by_ask = quantity * _current_ask->get_price(); auto xts_paid_by_ask = quantity; auto xts_received_by_bid = quantity; consumed_bid_depth += quantity; consumed_ask_depth += quantity; if( _current_bid->type == short_order ) { usd_paid_by_bid = usd_received_by_ask; } if( _current_ask->type == cover_order ) { usd_received_by_ask = usd_paid_by_bid; } FC_ASSERT( usd_paid_by_bid.amount >= 0 ); FC_ASSERT( xts_paid_by_ask.amount >= 0 ); FC_ASSERT( usd_received_by_ask.amount >= 0 ); FC_ASSERT( xts_received_by_bid.amount >= 0 ); FC_ASSERT( usd_paid_by_bid >= usd_received_by_ask ); FC_ASSERT( xts_paid_by_ask >= xts_received_by_bid ); // sanity check to keep supply from growing without bound FC_ASSERT( usd_paid_by_bid < asset(quote_asset->maximum_share_supply,quote_id), "", ("usd_paid_by_bid",usd_paid_by_bid)("asset",quote_asset) ); usd_fees_collected += usd_paid_by_bid - usd_received_by_ask; idump( (usd_fees_collected)(xts_paid_by_ask)(xts_received_by_bid)(quantity) ); market_transaction mtrx; mtrx.bid_owner = _current_bid->get_owner(); mtrx.ask_owner = _current_ask->get_owner(); mtrx.bid_price = _current_bid->get_price(); mtrx.ask_price = ask_price; mtrx.bid_paid = usd_paid_by_bid; mtrx.bid_received = xts_received_by_bid; mtrx.ask_paid = xts_paid_by_ask; mtrx.ask_received = usd_received_by_ask; mtrx.bid_type = _current_bid->type; mtrx.fees_collected = xts_paid_by_ask - xts_received_by_bid; _market_transactions.push_back(mtrx); trading_volume += mtrx.bid_received; market_stat->ask_depth -= xts_paid_by_ask.amount; if( _current_ask->type == ask_order ) { /* rounding errors on price cause this not to go to 0 in some cases */ if( quantity == _current_ask->get_quantity() ) _current_ask->state.balance = 0; else _current_ask->state.balance -= xts_paid_by_ask.amount; FC_ASSERT( _current_ask->state.balance >= 0 ); auto ask_balance_address = withdraw_condition( withdraw_with_signature(_current_ask->get_owner()), quote_id ).get_address(); auto ask_payout = _pending_state->get_balance_record( ask_balance_address ); if( !ask_payout ) ask_payout = balance_record( _current_ask->get_owner(), asset(0,quote_id), 0 ); ask_payout->balance += usd_received_by_ask.amount; ask_payout->last_update = _pending_state->now(); _pending_state->store_balance_record( *ask_payout ); _pending_state->store_ask_record( _current_ask->market_index, _current_ask->state ); } else if( _current_ask->type == cover_order ) { elog( "MATCHING COVER ORDER recv_usd: ${usd} paid_collat: ${c}", ("usd",usd_received_by_ask)("c",xts_paid_by_ask) ); wlog( "current ask: ${c}", ("c",_current_ask) ); // we are in the margin call range... _current_ask->state.balance -= usd_received_by_ask.amount; *(_current_ask->collateral) -= xts_paid_by_ask.amount; FC_ASSERT( _current_ask->state.balance >= 0 ); FC_ASSERT( *_current_ask->collateral >= 0 ); if( _current_ask->state.balance == 0 ) // no more USD left { // send collateral home to mommy & daddy wlog( " collateral balance is now 0!" ); auto ask_balance_address = withdraw_condition( withdraw_with_signature(_current_ask->get_owner()), base_id ).get_address(); auto ask_payout = _pending_state->get_balance_record( ask_balance_address ); if( !ask_payout ) ask_payout = balance_record( _current_ask->get_owner(), asset(0,base_id), 0 ); ask_payout->balance += (*_current_ask->collateral); ask_payout->last_update = _pending_state->now(); _pending_state->store_balance_record( *ask_payout ); _current_ask->collateral = 0; } wlog( "storing collateral ${c}", ("c",_current_ask) ); _pending_state->store_collateral_record( _current_ask->market_index, collateral_record( *_current_ask->collateral, _current_ask->state.balance ) ); } if( _current_bid->type == bid_order ) { _current_bid->state.balance -= usd_paid_by_bid.amount; FC_ASSERT( _current_bid->state.balance >= 0 ); auto bid_payout = _pending_state->get_balance_record( withdraw_condition( withdraw_with_signature(_current_bid->get_owner()), base_id ).get_address() ); if( !bid_payout ) bid_payout = balance_record( _current_bid->get_owner(), asset(0,base_id), 0 ); bid_payout->balance += xts_received_by_bid.amount; bid_payout->last_update = _pending_state->now(); _pending_state->store_balance_record( *bid_payout ); _pending_state->store_bid_record( _current_bid->market_index, _current_bid->state ); } else if( _current_bid->type == short_order ) { market_stat->bid_depth -= xts_received_by_bid.amount; // TODO: what if the amount paid is 0 for bid and ask due to rounding errors, // make sure this doesn't put us in an infinite loop. if( quantity == _current_bid->get_quantity() ) _current_bid->state.balance = 0; else _current_bid->state.balance -= xts_received_by_bid.amount; FC_ASSERT( _current_bid->state.balance >= 0 ); auto collateral = (xts_paid_by_ask + xts_received_by_bid).amount; auto cover_price = usd_received_by_ask / asset( (3*collateral)/4, base_id ); market_index_key cover_index( cover_price, _current_ask->get_owner() ); auto ocover_record = _pending_state->get_collateral_record( cover_index ); if( NOT ocover_record ) ocover_record = collateral_record(); ocover_record->collateral_balance += collateral; ocover_record->payoff_balance += usd_received_by_ask.amount; FC_ASSERT( ocover_record->payoff_balance >= 0 ); FC_ASSERT( ocover_record->collateral_balance >= 0 ); _pending_state->store_collateral_record( cover_index, *ocover_record ); _pending_state->store_short_record( _current_bid->market_index, _current_bid->state ); } } // while bid && ask if( quote_asset->is_market_issued() ) { if( !market_stat || market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 || market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ) FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) ); } _pending_state->store_market_status( *market_stat ); if( trading_volume.amount > 0 && get_next_bid() && get_next_ask() ) { market_history_key key(quote_id, base_id, market_history_key::each_block, _db_impl._head_block_header.timestamp); market_history_record new_record(_current_bid->get_price(), _current_ask->get_price(), trading_volume.amount); //LevelDB iterators are dumb and don't support proper past-the-end semantics. auto last_key_itr = _db_impl._market_history_db.lower_bound(key); if( !last_key_itr.valid() ) last_key_itr = _db_impl._market_history_db.last(); else --last_key_itr; key.timestamp = timestamp; //Unless the previous record for this market is the same as ours... if( (!(last_key_itr.valid() && last_key_itr.key().quote_id == quote_id && last_key_itr.key().base_id == base_id && last_key_itr.key().granularity == market_history_key::each_block && last_key_itr.value() == new_record)) ) { //...add a new entry to the history table. _pending_state->market_history[key] = new_record; } fc::time_point_sec start_of_this_hour = timestamp - (timestamp.sec_since_epoch() % (60*60)); market_history_key old_key(quote_id, base_id, market_history_key::each_hour, start_of_this_hour); if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) ) { auto old_record = *opt; old_record.volume += new_record.volume; if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask ) { old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid); old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask); _pending_state->market_history[old_key] = old_record; } } else _pending_state->market_history[old_key] = new_record; fc::time_point_sec start_of_this_day = timestamp - (timestamp.sec_since_epoch() % (60*60*24)); old_key = market_history_key(quote_id, base_id, market_history_key::each_day, start_of_this_day); if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) ) { auto old_record = *opt; old_record.volume += new_record.volume; if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask ) { old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid); old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask); _pending_state->market_history[old_key] = old_record; } } else _pending_state->market_history[old_key] = new_record; } auto market_state = _pending_state->get_market_status( quote_id, base_id ); if( !market_state ) market_state = market_status( quote_id, base_id, 0, 0 ); market_state->last_error.reset(); _pending_state->store_market_status( *market_state ); wlog( "done matching orders" ); _pending_state->apply_changes(); } catch( const fc::exception& e ) { wlog( "error executing market ${quote} / ${base}\n ${e}", ("quote",quote_id)("base",base_id)("e",e.to_detail_string()) ); auto market_state = _prior_state->get_market_status( quote_id, base_id ); if( !market_state ) market_state = market_status( quote_id, base_id, 0, 0 ); market_state->last_error = e; _prior_state->store_market_status( *market_state ); } } // execute(...)