signed_transaction wallet::transfer( const asset& amnt, const bts::address& to ) { try { auto change_address = get_new_address(); std::unordered_set<bts::address> req_sigs; asset total_in; signed_transaction trx; trx.inputs = my->collect_inputs( amnt, total_in, req_sigs ); asset change = total_in - amnt; trx.outputs.push_back( trx_output( claim_by_signature_output( to ), amnt) ); trx.outputs.push_back( trx_output( claim_by_signature_output( change_address ), change) ); trx.sigs.clear(); my->sign_transaction( trx, req_sigs ); uint32_t trx_bytes = fc::raw::pack( trx ).size(); asset fee( my->_current_fee_rate * trx_bytes ); if( amnt.unit == asset::bts ) { if( total_in >= amnt + fee ) { change = change - fee; trx.outputs.back() = trx_output( claim_by_signature_output( change_address ), change ); if( change == asset() ) trx.outputs.pop_back(); // no change required } else { elog( "NOT ENOUGH TO COVER AMOUNT + FEE... GRAB MORE.." ); // TODO: this function should be recursive here, but having 2x the fee should be good enough fee = fee + fee; // double the fee in this case to cover the growth req_sigs.clear(); total_in = asset(); trx.inputs = my->collect_inputs( amnt+fee, total_in, req_sigs ); change = total_in - amnt - fee; trx.outputs.back() = trx_output( claim_by_signature_output( change_address ), change ); if( change == asset() ) trx.outputs.pop_back(); // no change required } } else /// fee is in bts, but we are transferring something else { if( change.amount == fc::uint128_t(0) ) trx.outputs.pop_back(); // no change required // TODO: this function should be recursive here, but having 2x the fee should be good enough, some // transactions may overpay in this case, but this can be optimized later to reduce fees.. for now fee = fee + fee; // double the fee in this case to cover the growth asset total_fee_in; auto extra_in = my->collect_inputs( fee, total_fee_in, req_sigs ); trx.inputs.insert( trx.inputs.end(), extra_in.begin(), extra_in.end() ); trx.outputs.push_back( trx_output( claim_by_signature_output( change_address ), total_fee_in - fee ) ); } trx.sigs.clear(); my->sign_transaction(trx, req_sigs); return trx; } FC_RETHROW_EXCEPTIONS( warn, "${amnt} to ${to}", ("amnt",amnt)("to",to) ) }
signed_transaction wallet::short_sell( const asset& borrow_amnt, const price& ratio ) { try { auto amnt = borrow_amnt * ratio; FC_ASSERT( borrow_amnt.unit != asset::bts, "You cannot short sell BTS" ); auto change_address = get_new_address(); auto bts_in = amnt; signed_transaction trx; std::unordered_set<bts::address> req_sigs; asset total_in; asset in_with_fee = bts_in; // TODO: add fee proportional to trx size... trx.inputs = my->collect_inputs( in_with_fee, total_in, req_sigs ); asset change = total_in - bts_in; trx.outputs.push_back( trx_output( claim_by_long_output( change_address, ratio ), amnt) ); trx.outputs.push_back( trx_output( claim_by_signature_output( change_address ), change) ); trx.sigs.clear(); my->sign_transaction( trx, req_sigs ); uint32_t trx_bytes = fc::raw::pack( trx ).size(); asset fee( my->_current_fee_rate * trx_bytes ); if( total_in >= amnt + fee ) { change = change - fee; trx.outputs.back() = trx_output( claim_by_signature_output( change_address ), change ); if( change == asset() ) trx.outputs.pop_back(); // no change required } else { elog( "NOT ENOUGH TO COVER AMOUNT + FEE... GRAB MORE.." ); // TODO: this function should be recursive here, but having 2x the fee should be good enough fee = fee + fee; // double the fee in this case to cover the growth req_sigs.clear(); total_in = asset(); trx.inputs = my->collect_inputs( amnt+fee, total_in, req_sigs ); change = total_in - amnt - fee; trx.outputs.back() = trx_output( claim_by_signature_output( change_address ), change ); if( change == asset() ) trx.outputs.pop_back(); // no change required } trx.sigs.clear(); my->sign_transaction(trx, req_sigs); my->_data.open_short_sells[ output_reference( trx.id(), 0 ) ] = trx.outputs[0]; return trx; } FC_RETHROW_EXCEPTIONS( warn, "${amnt} @ ${price}", ("amnt",borrow_amnt)("price",ratio) ) }
signed_transaction wallet::cancel_bid( const output_reference& bid ) { try { signed_transaction trx; std::unordered_set<bts::address> req_sigs; auto bid_out_itr = my->_data.open_bids.find(bid); FC_ASSERT( bid_out_itr != my->_data.open_bids.end() ); auto bid_out = bid_out_itr->second.as<claim_by_bid_output>(); // TODO: collect fee for this transaction trx.inputs.push_back( trx_input( bid ) ); trx.outputs.push_back( trx_output( claim_by_signature_output( bid_out.pay_address ), bid_out_itr->second.amount, bid_out_itr->second.unit ) ); req_sigs.insert( bid_out.pay_address); my->sign_transaction( trx, req_sigs ); return trx; } FC_RETHROW_EXCEPTIONS( warn, "unable to find bid", ("bid",bid) ) }
/** * Pushes a new transaction into matched that pairs all bids/asks for a single quote/base pair */ void match_orders( std::vector<signed_transaction>& matched, asset::type quote, asset::type base ) { try { ilog( "match orders.." ); auto asks = _market_db.get_asks( quote, base ); auto bids = _market_db.get_bids( quote, base ); wlog( "asks: ${asks}", ("asks",asks) ); wlog( "bids: ${bids}", ("bids",bids) ); fc::optional<trx_output> ask_change; // stores a claim_by_bid or claim_by_long fc::optional<trx_output> bid_change; // stores a claim_by_bid address bid_payout_address; // current bid owner address ask_payout_address; signed_transaction market_trx; market_trx.timestamp = fc::time_point::now(); const uint64_t zero = 0ull; asset pay_asker( zero, quote ); asset pay_bidder( zero, base ); asset loan_amount( zero, quote ); asset collateral_amount(zero,base); asset bidder_change(zero,quote); // except for longs? asset asker_change(zero,base); /** asks are sorted from low to high, so we start * with the lowest ask, and check to see if there are * any bids that are greaterthan or equal to the ask, if * there are then either the full bid or full ask will be * filled. If the full bid is filled, then move on to the * next bid, and save the leftover ask. If the left over * ask is filled, then move to the next ask. * * When there are no more pairs that can be matched, exit * the loop and any partial payouts are made. */ auto ask_itr = asks.begin(); auto bid_itr = bids.rbegin(); trx_output working_ask; trx_output working_bid; if( ask_itr != asks.end() ) working_ask = get_output( ask_itr->location ); if( bid_itr != bids.rend() ) working_bid = get_output( bid_itr->location ); bool has_change = false; while( ask_itr != asks.end() && bid_itr != bids.rend() ) { // asset working_ask_tmp_amount; // store fractional working ask amounts here.. /* if( ask_change ) { working_ask = *ask_change; } else { working_ask = get_output( ask_itr->location ); working_ask_tmp_amount = working_ask.get_amount(); } if( bid_change ) { working_bid = *bid_change; } else { working_bid = get_output( bid_itr->location ); } */ claim_by_bid_output ask_claim = working_ask.as<claim_by_bid_output>(); wlog( "working bid: ${b}", ("b", working_bid ) ); wlog( "working ask: ${a}", ("a", working_ask ) ); ask_payout_address = ask_claim.pay_address; if( working_bid.claim_func == claim_by_long ) { auto long_claim = working_bid.as<claim_by_long_output>(); if( long_claim.ask_price < ask_claim.ask_price ) { ilog( "\n\n BID ${BID} >>>> ASK ${ASK}\n\n", ("BID",long_claim.ask_price)("ASK",ask_claim.ask_price) ); break; // exit the while loop, no more trades can occur } has_change = true; bid_payout_address = long_claim.pay_address; // for the purposes of shorts and longs, one asset type is BTS and the other // is one of the BitAssets which I will just call 'usd' for clairty. // The bids are an offer to take a short position which means they have an input // of BTS but are really offering USD to buy BTS... they BTS purchased with this // "new" USD is then placed into the collateral. asset bid_amount_bts = working_bid.get_amount(); // * long_claim.ask_price; asset bid_amount_usd = bid_amount_bts * long_claim.ask_price; asset ask_amount_bts = working_ask.get_amount(); // * long_claim.ask_price; asset ask_amount_usd = ask_amount_bts * ask_claim.ask_price; ilog( "ask_usd ${ask_usd} bid_usd ${bid_usd}", ("ask_usd",ask_amount_usd)("bid_usd",bid_amount_usd) ); ilog( "ask_bts ${ask_bts} bid_bts ${bid_bts}", ("ask_bts",ask_amount_bts)("bid_bts",bid_amount_bts) ); if( ask_amount_usd < bid_amount_usd ) { // then we have filled the ask pay_asker += ask_amount_usd; loan_amount += ask_amount_usd; collateral_amount += ask_amount_bts + ask_amount_usd * long_claim.ask_price; ilog( "bid amount bts ${bid_bts} - ${pay_asker} * ${price} = ${result}", ("bid_bts",bid_amount_bts)("pay_asker",pay_asker)("price",long_claim.ask_price)("result",pay_asker*long_claim.ask_price) ); bidder_change = bid_amount_bts - (ask_amount_usd * long_claim.ask_price); // ask_change.reset(); working_ask.amount = 0; // TODO: trunctate here??? working_bid.amount = bidder_change.get_rounded_amount(); // bid_change = working_bid; market_trx.inputs.push_back( ask_itr->location ); if( pay_asker.amount > static_cast<uint64_t>(0ull) ) market_trx.outputs.push_back( trx_output( claim_by_signature_output( ask_claim.pay_address ), pay_asker) ); pay_asker = asset(static_cast<uint64_t>(0ull),pay_asker.unit); ++ask_itr; if( ask_itr != asks.end() ) working_ask = get_output( ask_itr->location ); } else // we have filled the bid (short sell) { pay_asker += bid_amount_usd; loan_amount += bid_amount_usd; collateral_amount += bid_amount_bts + (bid_amount_usd * ask_claim.ask_price); ilog( "ask_amount_bts ${bid_bts} - ${loan_amount} * ${price} = ${result}", ("bid_bts",ask_amount_bts)("loan_amount",loan_amount)("price",ask_claim.ask_price)("result",loan_amount*ask_claim.ask_price) ); asker_change = ask_amount_bts - (bid_amount_usd* ask_claim.ask_price); working_bid.amount = 0; working_ask.amount = asker_change.get_rounded_amount(); // working_ask_tmp_amount = asker_change; ask_change = working_ask; market_trx.inputs.push_back( bid_itr->location ); market_trx.outputs.push_back( trx_output( claim_by_cover_output( loan_amount, long_claim.pay_address ), collateral_amount) ); loan_amount = asset(static_cast<uint64_t>(0ull),loan_amount.unit); collateral_amount = asset(); ++bid_itr; if( bid_itr != bids.rend() ) working_bid = get_output( bid_itr->location ); if( working_ask.amount < 10 ) { market_trx.inputs.push_back( ask_itr->location ); ilog( "ASK CLAIM ADDR ${A} amnt ${a}", ("A",ask_claim.pay_address)("a",pay_asker) ); if( pay_asker != asset(static_cast<uint64_t>(0ull),pay_asker.unit) ) { market_trx.outputs.push_back( trx_output( claim_by_signature_output( ask_claim.pay_address ), pay_asker) ); } pay_asker = asset(pay_asker.unit); ++ask_itr; if( ask_itr != asks.end() ) working_ask = get_output( ask_itr->location ); } } } else if( working_bid.claim_func == claim_by_bid ) { claim_by_bid_output bid_claim = working_bid.as<claim_by_bid_output>(); if( bid_claim.ask_price < ask_claim.ask_price ) { break; // exit the while loop, no more trades can occur } has_change = true; bid_payout_address = bid_claim.pay_address; // fort he purposese of long/long trades assets may be of any type, but // we will assume bids are in usd and asks are in bts for naming convention // purposes. asset bid_amount_usd = working_bid.get_amount(); asset bid_amount_bts = bid_amount_usd * bid_claim.ask_price; asset ask_amount_bts = working_ask.get_amount(); asset ask_amount_usd = ask_amount_bts * ask_claim.ask_price; ilog( "bid in ${b} expected ${e}", ("b",bid_amount_usd)("e",bid_amount_bts) ); ilog( "ask in ${a} expected ${e}", ("a",ask_amount_bts)("e",ask_amount_usd) ); if( ask_amount_usd.get_rounded_amount() < bid_amount_usd.get_rounded_amount() ) { // then we have filled the ask ilog("ilog ${x} < ${y}???", ("x",ask_amount_usd.amount)("y",bid_amount_usd.amount)); pay_asker += ask_amount_usd; ilog("."); auto delta_bidder = ask_amount_usd * bid_claim.ask_price; pay_bidder += delta_bidder; bidder_change = bid_amount_usd - delta_bidder * bid_claim.ask_price; ask_change.reset(); working_ask.amount = 0; working_bid.amount = bidder_change.get_rounded_amount(); bid_change = working_bid; market_trx.inputs.push_back( ask_itr->location ); ilog( "ASK CLAIM ADDR ${A} amnt ${a}", ("A",ask_claim.pay_address)("a",pay_asker) ); ilog( "BID CHANGE ${C}", ("C", working_bid ) ); if( pay_asker > asset(static_cast<uint64_t>(0ull),pay_asker.unit) ) { market_trx.outputs.push_back( trx_output( claim_by_signature_output( ask_claim.pay_address ), pay_asker) ); } pay_asker = asset(pay_asker.unit); ++ask_itr; if( ask_itr != asks.end() ) working_ask = get_output( ask_itr->location ); } else // then we have filled the bid or we have filled BOTH { ilog("."); pay_bidder += bid_amount_bts; ilog("."); auto delta_asker = bid_amount_bts * ask_claim.ask_price; pay_asker += delta_asker; working_bid.amount = 0; if( bid_amount_usd.get_rounded_amount() != ask_amount_usd.get_rounded_amount() ) { asker_change = ask_amount_bts - delta_asker * ask_claim.ask_price; working_ask.amount = asker_change.get_rounded_amount(); } else { working_ask.amount = 0; } market_trx.inputs.push_back( bid_itr->location ); ilog( "BID CLAIM ADDR ${A} ${a}", ("A",bid_claim.pay_address)("a",pay_bidder) ); market_trx.outputs.push_back( trx_output( claim_by_signature_output( bid_claim.pay_address ), pay_bidder) ); pay_bidder = asset(static_cast<uint64_t>(0ull),pay_bidder.unit); ++bid_itr; if( bid_itr != bids.rend() ) working_bid = get_output( bid_itr->location ); if( working_ask.amount < 10 ) { market_trx.inputs.push_back( ask_itr->location ); ilog( "ASK CLAIM ADDR ${A} amnt ${a}", ("A",ask_claim.pay_address)("a",pay_asker) ); if( pay_asker.amount > 0 ) market_trx.outputs.push_back( trx_output( claim_by_signature_output( ask_claim.pay_address ), pay_asker) ); pay_asker = asset(static_cast<uint64_t>(0ull),pay_asker.unit); ++ask_itr; if( ask_itr != asks.end() ) working_ask = get_output( ask_itr->location ); } } } else { FC_ASSERT( !"Bid must either be a claim by bid or claim by long", "", ("bid", working_bid) ); } } // while( ... ) if( has_change && working_ask.amount > 10 ) { FC_ASSERT( ask_itr != asks.end() ); if( pay_asker.amount > 0 ) { market_trx.inputs.push_back( ask_itr->location ); market_trx.outputs.push_back( working_ask ); market_trx.outputs.push_back( trx_output( claim_by_signature_output( ask_payout_address ), pay_asker ) ); } } if( has_change && working_bid.amount > 10 ) { FC_ASSERT( bid_itr != bids.rend() ); if( collateral_amount.amount > 10 ) { market_trx.inputs.push_back( bid_itr->location ); market_trx.outputs.push_back( working_bid ); market_trx.outputs.push_back( trx_output( claim_by_cover_output( loan_amount, bid_payout_address ), collateral_amount) ); } else if( working_bid.claim_func == claim_by_bid ) { if( pay_bidder.amount > 10 ) { market_trx.inputs.push_back( bid_itr->location ); market_trx.outputs.push_back( working_bid ); market_trx.outputs.push_back( trx_output( claim_by_signature_output( bid_payout_address ), pay_bidder ) ); } } } wlog( "Market Transaction: ${trx}", ("trx", market_trx) ); if( market_trx.inputs.size() ) { FC_ASSERT( market_trx.outputs.size() ); FC_ASSERT( market_trx.inputs.size() ); matched.push_back(market_trx); } //ilog( "done match orders.." ); } FC_RETHROW_EXCEPTIONS( warn, "", ("quote",quote)("base",base) ) }
void match_orders( std::vector<signed_transaction>& matched, asset::type quote, asset::type base ) { try { ilog( "match orders.." ); auto bids = _market_db.get_bids( quote, base ); auto asks = _market_db.get_asks( quote, base ); wlog( "asks: ${asks}", ("asks",asks) ); wlog( "bids: ${bids}", ("bids",bids) ); fc::optional<trx_output> ask_change; fc::optional<trx_output> bid_change; fc::optional<trx_output> cover_change; address bid_payout_address; fc::optional<asset> bid_payout; asset cover_collat; fc::optional<claim_by_cover_output> cover_payout; signed_transaction market_trx; market_trx.timestamp = fc::time_point::now(); /** asks are sorted from low to high, so we start * with the lowest ask, and check to see if there are * any bids that are greaterthan or equal to the ask, if * there are then either the full bid or full ask will be * filled. If the full bid is filled, then move on to the * next bid, and save the leftover ask. If the left over * ask is filled, then move to the next ask. * * When there are no more pairs that can be matched, exit * the loop and any partial payouts are made. */ auto ask_itr = asks.begin(); auto bid_itr = bids.rbegin(); while( ask_itr != asks.end() && bid_itr != bids.rend() ) { trx_output working_ask; trx_output working_bid; if( ask_change ) { working_ask = *ask_change; } else { working_ask = get_output( ask_itr->location ); } if( bid_change ) { working_bid = *bid_change; } else { working_bid = get_output( bid_itr->location); } claim_by_bid_output bid_claim = working_bid.as<claim_by_bid_output>(); if( working_ask.claim_func == claim_by_long ) { auto long_claim = working_ask.as<claim_by_long_output>(); if( long_claim.ask_price > bid_claim.ask_price ) { break; // exit the while loop, no more trades can occur } asset bid_amount = working_bid.get_amount() * bid_claim.ask_price; asset ask_amount = working_ask.get_amount() * long_claim.ask_price; FC_ASSERT( bid_amount.unit == ask_amount.unit ); auto trade_amount = std::min(bid_amount,ask_amount); ilog( "bid amount: ${b} @ ${bp} ask amount: ${a} @ ${ap}", ("b",bid_amount)("a",ask_amount)("bp",bid_claim.ask_price)("ap",long_claim.ask_price) ); asset bid_change_amount = working_bid.get_amount(); bid_change_amount -= trade_amount * bid_claim.ask_price; ilog( "bid change.. ${c}", ("c",bid_change_amount) ); asset ask_change_amount = working_ask.get_amount(); ask_change_amount -= trade_amount * long_claim.ask_price; ilog( "ask change.. ${c}", ("c",ask_change_amount) ); if( ask_change_amount != bid_change_amount && ask_change_amount != asset(0,working_bid.unit) ) { FC_ASSERT( !"At least one of the bid or ask should be completely filled", "", ("ask_change_amount",ask_change_amount)("bid_change_amount",bid_change_amount) ); } bid_payout_address = bid_claim.pay_address; auto bid_payout_amount = bid_amount - (bid_change_amount * bid_claim.ask_price); if( bid_payout ) { *bid_payout += bid_payout_amount; } else { bid_payout = bid_payout_amount; } if( cover_payout ) { cover_payout->payoff_amount += trade_amount.get_rounded_amount(); cover_collat += (trade_amount * long_claim.ask_price)*2; } else { cover_payout = claim_by_cover_output(); cover_payout->owner = long_claim.pay_address; cover_payout->payoff_unit = trade_amount.unit; cover_payout->payoff_amount = trade_amount.get_rounded_amount(); cover_collat = (trade_amount * long_claim.ask_price)*2; } if( bid_change_amount != asset(0, working_bid.unit) ) { // TODO: accumulate fractional parts, round at the end?.... working_bid.amount = bid_change_amount.get_rounded_amount(); bid_change = working_bid; elog( "we DID NOT fill the bid..." ); } else // we have filled the bid! { elog( "we filled the bid..." ); market_trx.inputs.push_back( bid_itr->location ); market_trx.outputs.push_back( trx_output( claim_by_signature_output( bid_claim.pay_address ), bid_payout->get_rounded_asset() ) ); bid_change.reset(); bid_payout.reset(); ++bid_itr; } if( ask_change_amount != asset( 0, working_bid.unit ) ) { working_ask.amount = ask_change_amount.get_rounded_amount(); ask_change = working_ask; } else // we have filled the ask! { market_trx.inputs.push_back( ask_itr->location ); market_trx.outputs.push_back( trx_output( *cover_payout, cover_collat ) ); ask_change.reset(); cover_payout.reset(); ++ask_itr; } } else if( working_ask.claim_func == claim_by_bid ) { FC_ASSERT( !"Not Implemented" ); claim_by_bid_output ask_claim = working_ask.as<claim_by_bid_output>(); if( ask_claim.ask_price > bid_claim.ask_price ) { break; } // TODO: implement straight trades.. } else { FC_ASSERT( !"Ask must either be a claim by bid or claim by long", "", ("ask", working_ask) ); } } // while( ... ) if( ask_change && ask_itr != asks.end() ) market_trx.inputs.push_back( ask_itr->location ); if( bid_change && bid_itr != bids.rend() ) market_trx.inputs.push_back( bid_itr->location ); if( ask_change ) { ilog( "ask_change: ${ask_change}", ("ask_change",ask_change) ); market_trx.outputs.push_back( *ask_change ); } if( bid_change ) { ilog( "bid_change: ${bid_change}", ("bid_change",bid_change) ); market_trx.outputs.push_back( *bid_change ); } if( bid_payout ) { ilog( "bid_payout ${payout}", ("payout",bid_payout) ); market_trx.outputs.push_back( trx_output( claim_by_signature_output( bid_payout_address ), *bid_payout ) ); } else { wlog ( "NO BID PAYOUT" ); } if( cover_payout ) { ilog( "cover_payout ${payout}", ("payout",cover_payout) ); market_trx.outputs.push_back( trx_output( *cover_payout, cover_collat ) ); } wlog( "Market Transaction: ${trx}", ("trx", market_trx) ); if( market_trx.inputs.size() ) { FC_ASSERT( market_trx.outputs.size() ); matched.push_back(market_trx); } //ilog( "done match orders.." ); } FC_RETHROW_EXCEPTIONS( warn, "", ("quote",quote)("base",base) ) }