static CreateOfferResult applyCreateOfferHelper(Application& app, LedgerDelta& delta, uint64 offerId, SecretKey& source, Currency& takerGets, Currency& takerPays, Price const& price, int64_t amount, SequenceNumber seq) { uint64_t expectedOfferID = delta.getHeaderFrame().getLastGeneratedID() + 1; if (offerId != 0) { expectedOfferID = offerId; } TransactionFramePtr txFrame; txFrame = createOfferOp(offerId, source, takerGets, takerPays, price, amount, seq); txFrame->apply(delta, app); checkTransaction(*txFrame); auto& results = txFrame->getResult().result.results(); REQUIRE(results.size() == 1); auto& createOfferResult = results[0].tr().createOfferResult(); if (createOfferResult.code() == CREATE_OFFER_SUCCESS) { OfferFrame offer; auto& offerResult = createOfferResult.success().offer; auto& offerEntry = offer.getOffer(); switch (offerResult.effect()) { case CREATE_OFFER_CREATED: case CREATE_OFFER_UPDATED: REQUIRE(OfferFrame::loadOffer(source.getPublicKey(), expectedOfferID, offer, app.getDatabase())); REQUIRE(memcmp(&offerEntry, &offerResult.offer(), sizeof(OfferEntry)) == 0); REQUIRE(offerEntry.price == price); REQUIRE(memcmp(&offerEntry.takerGets, &takerGets, sizeof(Currency)) == 0); REQUIRE(memcmp(&offerEntry.takerPays, &takerPays, sizeof(Currency)) == 0); break; case CREATE_OFFER_DELETED: REQUIRE(!OfferFrame::loadOffer(source.getPublicKey(), expectedOfferID, offer, app.getDatabase())); break; default: abort(); } } return createOfferResult; }
OfferExchange::CrossOfferResult OfferExchange::crossOffer(OfferFrame& sellingWheatOffer, int64_t maxWheatReceived, int64_t& numWheatReceived, int64_t maxSheepSend, int64_t& numSheepSend) { Asset& sheep = sellingWheatOffer.getOffer().buying; Asset& wheat = sellingWheatOffer.getOffer().selling; AccountID& accountBID = sellingWheatOffer.getOffer().sellerID; Database& db = mLedgerManager.getDatabase(); AccountFrame::pointer accountB; accountB = AccountFrame::loadAccount(accountBID, db); if (!accountB) { throw std::runtime_error( "invalid database state: offer must have matching account"); } TrustFrame::pointer wheatLineAccountB; if (wheat.type() != ASSET_TYPE_NATIVE) { wheatLineAccountB = TrustFrame::loadTrustLine(accountBID, wheat, db); if (!wheatLineAccountB) { throw std::runtime_error( "invalid database state: offer must have matching trust line"); } } TrustFrame::pointer sheepLineAccountB; if (sheep.type() == ASSET_TYPE_NATIVE) { numWheatReceived = INT64_MAX; } else { sheepLineAccountB = TrustFrame::loadTrustLine(accountBID, sheep, db); if (!sheepLineAccountB) { throw std::runtime_error( "invalid database state: offer must have matching trust line"); } // compute numWheatReceived based on what the account can receive int64_t sellerMaxSheep = sheepLineAccountB->getMaxAmountReceive(); if (!bigDivide(numWheatReceived, sellerMaxSheep, sellingWheatOffer.getOffer().price.d, sellingWheatOffer.getOffer().price.n)) { numWheatReceived = INT64_MAX; } } // adjust numWheatReceived with what the seller has { int64_t wheatCanSell; if (wheat.type() == ASSET_TYPE_NATIVE) { // can only send above the minimum balance wheatCanSell = accountB->getBalanceAboveReserve(mLedgerManager); } else { if (wheatLineAccountB->isAuthorized()) { wheatCanSell = wheatLineAccountB->getBalance(); } else { wheatCanSell = 0; } } if (numWheatReceived > wheatCanSell) { numWheatReceived = wheatCanSell; } } // you can receive the lesser of the amount of wheat offered or // the amount the guy has if (numWheatReceived >= sellingWheatOffer.getOffer().amount) { numWheatReceived = sellingWheatOffer.getOffer().amount; } else { // update the offer based on the balance (to determine if it should be // deleted or not) // note that we don't need to write into the db at this point as the // actual update // is done further down sellingWheatOffer.getOffer().amount = numWheatReceived; } bool reducedOffer = false; if (numWheatReceived > maxWheatReceived) { numWheatReceived = maxWheatReceived; reducedOffer = true; } // this guy can get X wheat to you. How many sheep does that get him? if (!bigDivide(numSheepSend, numWheatReceived, sellingWheatOffer.getOffer().price.n, sellingWheatOffer.getOffer().price.d)) { numSheepSend = INT64_MAX; } if (numSheepSend > maxSheepSend) { // reduce the number even more if there is a limit on Sheep numSheepSend = maxSheepSend; reducedOffer = true; } // bias towards seller (this cannot overflow at this point) numWheatReceived = bigDivide(numSheepSend, sellingWheatOffer.getOffer().price.d, sellingWheatOffer.getOffer().price.n); bool offerTaken = false; if (numWheatReceived == 0 || numSheepSend == 0) { if (reducedOffer) { return eOfferCantConvert; } else { // force delete the offer as it represents a bogus offer numWheatReceived = 0; numSheepSend = 0; offerTaken = true; } } offerTaken = offerTaken || sellingWheatOffer.getOffer().amount <= numWheatReceived; if (offerTaken) { // entire offer is taken sellingWheatOffer.storeDelete(mDelta, db); accountB->addNumEntries(-1, mLedgerManager); accountB->storeChange(mDelta, db); } else { sellingWheatOffer.getOffer().amount -= numWheatReceived; sellingWheatOffer.storeChange(mDelta, db); } // Adjust balances if (sheep.type() == ASSET_TYPE_NATIVE) { accountB->getAccount().balance += numSheepSend; accountB->storeChange(mDelta, db); } else { if (!sheepLineAccountB->addBalance(numSheepSend)) { return eOfferCantConvert; } sheepLineAccountB->storeChange(mDelta, db); } if (wheat.type() == ASSET_TYPE_NATIVE) { accountB->getAccount().balance -= numWheatReceived; accountB->storeChange(mDelta, db); } else { if (!wheatLineAccountB->addBalance(-numWheatReceived)) { return eOfferCantConvert; } wheatLineAccountB->storeChange(mDelta, db); } mOfferTrail.push_back( ClaimOfferAtom(accountB->getID(), sellingWheatOffer.getOfferID(), wheat, numWheatReceived, sheep, numSheepSend)); return offerTaken ? eOfferTaken : eOfferPartial; }