mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-28 06:55:50 +00:00
Add tfSell support for OfferCreate.
This commit is contained in:
@@ -986,18 +986,19 @@ STAmount STAmount::setRate(uint64 rate)
|
||||
return STAmount(CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent);
|
||||
}
|
||||
|
||||
// Existing offer is on the books.
|
||||
// Price is offer owner's, which might be better for taker.
|
||||
// Taker pays what they can.
|
||||
// Taker gets all taker can pay for with saTakerFunds/uTakerPaysRate, limited by saOfferPays and saOfferFunds/uOfferPaysRate.
|
||||
//
|
||||
// Existing offer is on the books. Offer owner gets their rate.
|
||||
//
|
||||
// Taker pays what they can. If taker is an offer, doesn't matter what rate taker is. Taker is spending at same or better rate
|
||||
// than they wanted. Taker should consider themselves as wanting to buy X amount. Taker is willing to pay at most the rate of Y/X
|
||||
// each. Therefore, after having some part of their offer fulfilled at a better rate their offer should be reduced accordingly.
|
||||
//
|
||||
// YYY Could have a flag for spend up to behaviour vs current limit spend rate.
|
||||
// If taker is an offer, taker is spending at same or better rate than they wanted.
|
||||
// Taker should consider themselves as wanting to buy X amount.
|
||||
// Taker is willing to pay at most the rate of Y/X each.
|
||||
// Buy semantics:
|
||||
// - After having some part of their offer fulfilled at a better rate their offer should be reduced accordingly.
|
||||
//
|
||||
// There are no quality costs for offer vs offer taking.
|
||||
//
|
||||
// --> bSell: True for sell semantics.
|
||||
// --> uTakerPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by taker.
|
||||
// --> uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
|
||||
// --> saOfferRate: Original saOfferGets/saOfferPays, when offer was made.
|
||||
@@ -1012,6 +1013,7 @@ STAmount STAmount::setRate(uint64 rate)
|
||||
// <-- saTakerIssuerFee: Actual
|
||||
// <-- saOfferIssuerFee: Actual
|
||||
bool STAmount::applyOffer(
|
||||
const bool bSell,
|
||||
const uint32 uTakerPaysRate, const uint32 uOfferPaysRate,
|
||||
const STAmount& saOfferRate,
|
||||
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
|
||||
@@ -1022,21 +1024,21 @@ bool STAmount::applyOffer(
|
||||
{
|
||||
saOfferGets.throwComparable(saTakerFunds);
|
||||
|
||||
assert(!saOfferFunds.isZero() && !saTakerFunds.isZero()); // Must have funds.
|
||||
assert(!saOfferGets.isZero() && !saOfferPays.isZero()); // Must not be a null offer.
|
||||
assert(!saOfferFunds.isZero() && !saTakerFunds.isZero()); // Both must have funds.
|
||||
assert(saOfferGets.isPositive() && saOfferPays.isPositive()); // Must not be a null offer.
|
||||
|
||||
// Limit offerer funds available, by transfer fees.
|
||||
STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate
|
||||
? saOfferFunds
|
||||
: STAmount::divide(saOfferFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9));
|
||||
? saOfferFunds // As is.
|
||||
: STAmount::divide(saOfferFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); // Reduce by offer fees.
|
||||
|
||||
cLog(lsINFO) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
|
||||
cLog(lsINFO) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText();
|
||||
|
||||
// Limit taker funds available, by transfer fees.
|
||||
STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate
|
||||
? saTakerFunds
|
||||
: STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9));
|
||||
? saTakerFunds // As is.
|
||||
: STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); // Reduce by taker fees.
|
||||
|
||||
cLog(lsINFO) << "applyOffer: TAKER_FEES=" << STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText();
|
||||
cLog(lsINFO) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
|
||||
@@ -1069,12 +1071,14 @@ bool STAmount::applyOffer(
|
||||
cLog(lsINFO) << "applyOffer: saTakerPaysMax=" << saTakerPaysMax.getFullText();
|
||||
STAmount saTakerGetsMax = saTakerPaysMax >= saOfferGetsAvailable
|
||||
? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans.
|
||||
: std::min(saOfferPaysAvailable, divRound(saTakerPaysMax, saOfferRate, saTakerGets, !saTakerGets.isNative())); // Taker a portion of offer.
|
||||
: std::min(saOfferPaysAvailable, divRound(saTakerPaysMax, saOfferRate, saTakerGets, true)); // Taker a portion of offer.
|
||||
cLog(lsINFO) << "applyOffer: saOfferRate=" << saOfferRate.getFullText();
|
||||
cLog(lsINFO) << "applyOffer: saTakerGetsMax=" << saTakerGetsMax.getFullText();
|
||||
|
||||
saTakerGot = std::min(saTakerGets, saTakerGetsMax); // Limit by wanted.
|
||||
saTakerPaid = saTakerGot == saOfferPaysAvailable
|
||||
saTakerGot = bSell
|
||||
? saTakerGetsMax // Get all available that are paid for.
|
||||
: std::min(saTakerGets, saTakerGetsMax); // Limit by wanted.
|
||||
saTakerPaid = saTakerGot >= saOfferPaysAvailable
|
||||
? saOfferGetsAvailable
|
||||
: std::min(saOfferGetsAvailable, mulRound(saTakerGot, saOfferRate, saTakerFunds, true));
|
||||
|
||||
@@ -1120,7 +1124,7 @@ bool STAmount::applyOffer(
|
||||
|
||||
cLog(lsINFO) << "applyOffer: saTakerGot=" << saTakerGot.getFullText();
|
||||
|
||||
return saTakerGot >= saOfferPaysAvailable;
|
||||
return saTakerGot >= saOfferPaysAvailable; // True, if consumed offer.
|
||||
}
|
||||
|
||||
STAmount STAmount::getPay(const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed)
|
||||
|
||||
@@ -92,6 +92,7 @@ bool OfferCreateTransactor::bValidOffer(
|
||||
TER OfferCreateTransactor::takeOffers(
|
||||
const bool bOpenLedger,
|
||||
const bool bPassive,
|
||||
const bool bSell,
|
||||
const uint256& uBookBase,
|
||||
const uint160& uTakerAccountID,
|
||||
SLE::ref sleTakerAccount,
|
||||
@@ -110,7 +111,7 @@ TER OfferCreateTransactor::takeOffers(
|
||||
|
||||
assert(saTakerPays && saTakerGets);
|
||||
|
||||
cLog(lsINFO) << "takeOffers: against book: " << uBookBase.ToString();
|
||||
cLog(lsINFO) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString();
|
||||
|
||||
LedgerEntrySet& lesActive = mEngine->getNodes();
|
||||
uint256 uTipIndex = uBookBase;
|
||||
@@ -244,6 +245,7 @@ TER OfferCreateTransactor::takeOffers(
|
||||
cLog(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText();
|
||||
|
||||
bool bOfferDelete = STAmount::applyOffer(
|
||||
bSell,
|
||||
lesActive.rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID),
|
||||
lesActive.rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
|
||||
saOfferRate,
|
||||
@@ -313,7 +315,10 @@ TER OfferCreateTransactor::takeOffers(
|
||||
if (tesSUCCESS == terResult)
|
||||
terResult = lesActive.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner.
|
||||
|
||||
// Reduce amount considered paid by taker's rate (not actual cost).
|
||||
if (!bSell)
|
||||
{
|
||||
// Buy semantics: Reduce amount considered paid by taker's rate. Not by actual cost which is lower.
|
||||
// That is, take less as to just satify our buy requirement.
|
||||
STAmount saTakerCould = saTakerPays - saTakerPaid; // Taker could pay.
|
||||
if (saTakerFunds < saTakerCould)
|
||||
saTakerCould = saTakerFunds;
|
||||
@@ -325,7 +330,9 @@ TER OfferCreateTransactor::takeOffers(
|
||||
cLog(lsINFO) << "takeOffers: applyOffer: saTakerRate: " << saTakerRate.getFullText();
|
||||
cLog(lsINFO) << "takeOffers: applyOffer: saTakerUsed: " << saTakerUsed.getFullText();
|
||||
|
||||
saTakerPaid += std::min(saTakerCould, saTakerUsed);
|
||||
saSubTakerPaid = std::min(saTakerCould, saTakerUsed);
|
||||
}
|
||||
saTakerPaid += saSubTakerPaid;
|
||||
saTakerGot += saSubTakerGot;
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
@@ -362,6 +369,7 @@ TER OfferCreateTransactor::doApply()
|
||||
const bool bPassive = isSetBit(uTxFlags, tfPassive);
|
||||
const bool bImmediateOrCancel = isSetBit(uTxFlags, tfImmediateOrCancel);
|
||||
const bool bFillOrKill = isSetBit(uTxFlags, tfFillOrKill);
|
||||
const bool bSell = isSetBit(uTxFlags, tfSell);
|
||||
STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays);
|
||||
STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets);
|
||||
|
||||
@@ -498,12 +506,13 @@ TER OfferCreateTransactor::doApply()
|
||||
terResult = takeOffers(
|
||||
bOpenLedger,
|
||||
bPassive,
|
||||
bSell,
|
||||
uTakeBookBase,
|
||||
mTxnAccountID,
|
||||
sleCreator,
|
||||
saTakerGets, // Reverse as we are the taker for taking.
|
||||
saTakerPays,
|
||||
saPaid, // How much would have spent at full price.
|
||||
saPaid, // Buy semantics: how much would have sold at full price. Sell semantics: how much was sold.
|
||||
saGot, // How much was got.
|
||||
bUnfunded);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ protected:
|
||||
TER takeOffers(
|
||||
const bool bOpenLedger,
|
||||
const bool bPassive,
|
||||
const bool bSell,
|
||||
const uint256& uBookBase,
|
||||
const uint160& uTakerAccountID,
|
||||
SLE::ref sleTakerAccount,
|
||||
|
||||
@@ -451,6 +451,7 @@ public:
|
||||
// Someone is offering X for Y, I try to pay Z, how much do I get?
|
||||
// And what's left of the offer? And how much do I actually pay?
|
||||
static bool applyOffer(
|
||||
const bool bSell,
|
||||
const uint32 uTakerPaysRate, const uint32 uOfferPaysRate,
|
||||
const STAmount& saOfferRate,
|
||||
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
|
||||
|
||||
@@ -74,7 +74,8 @@ const uint32 tfAccountSetMask = ~(tfRequireDestTag|tfOptionalDestTag
|
||||
const uint32 tfPassive = 0x00010000;
|
||||
const uint32 tfImmediateOrCancel = 0x00020000;
|
||||
const uint32 tfFillOrKill = 0x00040000;
|
||||
const uint32 tfOfferCreateMask = ~(tfPassive|tfImmediateOrCancel|tfFillOrKill);
|
||||
const uint32 tfSell = 0x00080000;
|
||||
const uint32 tfOfferCreateMask = ~(tfPassive|tfImmediateOrCancel|tfFillOrKill|tfSell);
|
||||
|
||||
// Payment flags:
|
||||
const uint32 tfNoRippleDirect = 0x00010000;
|
||||
|
||||
@@ -1547,4 +1547,195 @@ buster.testCase("Offer tests 3", {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
buster.testCase("Offer tfSell", {
|
||||
'setUp' : testutils.build_setup(),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true }),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, standalone: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
"basic sell" :
|
||||
function (done) {
|
||||
var self = this;
|
||||
var final_create;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
// Provide micro amounts to compensate for fees to make results round nice.
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "350.000020", ["alice", "bob", "mtgox"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"alice" : "1000/USD/mtgox",
|
||||
"bob" : "1000/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Distribute funds.";
|
||||
|
||||
testutils.payments(self.remote,
|
||||
{
|
||||
"mtgox" : [ "500/USD/bob" ],
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Create offer bob.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("bob", "200.0", "200/USD/mtgox")
|
||||
.set_flags('Sell') // Should not matter at all.
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
// Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
|
||||
// Ask for more than available to prove reserve works.
|
||||
self.what = "Create offer alice.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("alice", "100/USD/mtgox", "100.0")
|
||||
.set_flags('Sell') // Should not matter at all.
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
// function (callback) {
|
||||
// self.what = "Display ledger";
|
||||
//
|
||||
// self.remote.request_ledger('current', true)
|
||||
// .on('success', function (m) {
|
||||
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
function (callback) {
|
||||
self.what = "Verify balances.";
|
||||
|
||||
testutils.verify_balances(self.remote,
|
||||
{
|
||||
"alice" : [ "100/USD/mtgox", "250.0" ],
|
||||
"bob" : "400/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
], function (error) {
|
||||
// console.log("result: error=%s", error);
|
||||
buster.refute(error);
|
||||
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
"2x sell exceed limit" :
|
||||
function (done) {
|
||||
var self = this;
|
||||
var final_create;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
// Provide micro amounts to compensate for fees to make results round nice.
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "350.000020", ["alice", "bob", "mtgox"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"alice" : "150/USD/mtgox",
|
||||
"bob" : "1000/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Distribute funds.";
|
||||
|
||||
testutils.payments(self.remote,
|
||||
{
|
||||
"mtgox" : [ "500/USD/bob" ],
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Create offer bob.";
|
||||
|
||||
// Taker pays 200 XRP for 100 USD.
|
||||
// Selling USD.
|
||||
self.remote.transaction()
|
||||
.offer_create("bob", "100.0", "200/USD/mtgox")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
// Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
|
||||
// Ask for more than available to prove reserve works.
|
||||
self.what = "Create offer alice.";
|
||||
|
||||
// Taker pays 100 USD for 100 XRP.
|
||||
// Selling XRP.
|
||||
// Will sell all 100 XRP and get more USD than asked for.
|
||||
self.remote.transaction()
|
||||
.offer_create("alice", "100/USD/mtgox", "100.0")
|
||||
.set_flags('Sell')
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
// function (callback) {
|
||||
// self.what = "Display ledger";
|
||||
//
|
||||
// self.remote.request_ledger('current', true)
|
||||
// .on('success', function (m) {
|
||||
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
function (callback) {
|
||||
self.what = "Verify balances.";
|
||||
|
||||
testutils.verify_balances(self.remote,
|
||||
{
|
||||
"alice" : [ "200/USD/mtgox", "250.0" ],
|
||||
"bob" : "300/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
], function (error) {
|
||||
// console.log("result: error=%s", error);
|
||||
buster.refute(error);
|
||||
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
Reference in New Issue
Block a user