mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-01 08:25:51 +00:00
Add support for offers to allow parties to go into debt. Broken.
This commit is contained in:
@@ -1023,25 +1023,51 @@ uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint16
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative.
|
// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative.
|
||||||
// <-- IOU's uAccountID has of uIssuerID
|
// <-- IOU's uAccountID has of uIssuerID.
|
||||||
STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID)
|
STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail)
|
||||||
{
|
{
|
||||||
STAmount saBalance;
|
STAmount saBalance;
|
||||||
SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID));
|
SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID));
|
||||||
|
|
||||||
if (sleRippleState)
|
if (!sleRippleState)
|
||||||
{
|
{
|
||||||
saBalance = sleRippleState->getFieldAmount(sfBalance);
|
saBalance.zero(uCurrencyID, uIssuerID);
|
||||||
|
}
|
||||||
if (uAccountID > uIssuerID)
|
else if (uAccountID > uIssuerID)
|
||||||
|
{
|
||||||
|
if (bAvail)
|
||||||
|
{
|
||||||
|
saBalance = sleRippleState->getFieldAmount(sfLowLimit);
|
||||||
|
saBalance -= sleRippleState->getFieldAmount(sfBalance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saBalance = sleRippleState->getFieldAmount(sfBalance);
|
||||||
saBalance.negate(); // Put balance in uAccountID terms.
|
saBalance.negate(); // Put balance in uAccountID terms.
|
||||||
|
}
|
||||||
|
|
||||||
|
saBalance.setIssuer(uIssuerID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (bAvail)
|
||||||
|
{
|
||||||
|
saBalance = sleRippleState->getFieldAmount(sfHighLimit);
|
||||||
|
saBalance += sleRippleState->getFieldAmount(sfBalance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saBalance = sleRippleState->getFieldAmount(sfBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
saBalance.setIssuer(uIssuerID);
|
||||||
}
|
}
|
||||||
|
|
||||||
return saBalance;
|
return saBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative.
|
// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative.
|
||||||
STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID)
|
STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail)
|
||||||
{
|
{
|
||||||
STAmount saAmount;
|
STAmount saAmount;
|
||||||
|
|
||||||
@@ -1053,12 +1079,13 @@ STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160&
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID);
|
saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID, bAvail);
|
||||||
}
|
}
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s")
|
cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s bAvail=%d")
|
||||||
% RippleAddress::createHumanAccountID(uAccountID)
|
% RippleAddress::createHumanAccountID(uAccountID)
|
||||||
% saAmount.getFullText());
|
% saAmount.getFullText()
|
||||||
|
% bAvail);
|
||||||
|
|
||||||
return saAmount;
|
return saAmount;
|
||||||
}
|
}
|
||||||
@@ -1068,7 +1095,7 @@ STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160&
|
|||||||
// --> saDefault/currency/issuer
|
// --> saDefault/currency/issuer
|
||||||
// <-- saFunds: Funds available. May be negative.
|
// <-- saFunds: Funds available. May be negative.
|
||||||
// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault.
|
// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault.
|
||||||
STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault)
|
STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault, bool bAvail)
|
||||||
{
|
{
|
||||||
STAmount saFunds;
|
STAmount saFunds;
|
||||||
|
|
||||||
@@ -1082,12 +1109,13 @@ STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount&
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer());
|
saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer(), bAvail);
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s")
|
cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s bAvail=%d")
|
||||||
% RippleAddress::createHumanAccountID(uAccountID)
|
% RippleAddress::createHumanAccountID(uAccountID)
|
||||||
% saDefault.getFullText()
|
% saDefault.getFullText()
|
||||||
% saFunds.getFullText());
|
% saFunds.getFullText()
|
||||||
|
% bAvail);
|
||||||
}
|
}
|
||||||
|
|
||||||
return saFunds;
|
return saFunds;
|
||||||
|
|||||||
@@ -119,14 +119,14 @@ public:
|
|||||||
uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID)
|
uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID)
|
||||||
{ return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); }
|
{ return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); }
|
||||||
|
|
||||||
STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail=false);
|
||||||
STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount);
|
STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount);
|
||||||
void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true);
|
void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true);
|
||||||
STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
||||||
|
|
||||||
STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail=false);
|
||||||
|
STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault, bool bAvail=false);
|
||||||
void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
||||||
STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault);
|
|
||||||
|
|
||||||
Json::Value getJson(int) const;
|
Json::Value getJson(int) const;
|
||||||
void calcRawMeta(Serializer&, TER result, uint32 index);
|
void calcRawMeta(Serializer&, TER result, uint32 index);
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ TER OfferCreateTransactor::takeOffers(
|
|||||||
|
|
||||||
Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText();
|
Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText();
|
||||||
|
|
||||||
STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays);
|
STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays, true);
|
||||||
STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays);
|
STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays, true);
|
||||||
SLE::pointer sleOfferAccount; // Owner of offer.
|
SLE::pointer sleOfferAccount; // Owner of offer.
|
||||||
|
|
||||||
if (!saOfferFunds.isPositive())
|
if (!saOfferFunds.isPositive())
|
||||||
@@ -317,7 +317,7 @@ TER OfferCreateTransactor::doApply()
|
|||||||
|
|
||||||
terResult = temBAD_ISSUER;
|
terResult = temBAD_ISSUER;
|
||||||
}
|
}
|
||||||
else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive())
|
else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive())
|
||||||
{
|
{
|
||||||
Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded.";
|
Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded.";
|
||||||
|
|
||||||
@@ -348,7 +348,6 @@ TER OfferCreateTransactor::doApply()
|
|||||||
% saTakerPays.getFullText());
|
% saTakerPays.getFullText());
|
||||||
|
|
||||||
// Take using the parameters of the offer.
|
// Take using the parameters of the offer.
|
||||||
#if 1
|
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText();
|
||||||
terResult = takeOffers(
|
terResult = takeOffers(
|
||||||
bPassive,
|
bPassive,
|
||||||
@@ -360,9 +359,7 @@ TER OfferCreateTransactor::doApply()
|
|||||||
saOfferPaid, // How much was spent.
|
saOfferPaid, // How much was spent.
|
||||||
saOfferGot // How much was got.
|
saOfferGot // How much was got.
|
||||||
);
|
);
|
||||||
#else
|
|
||||||
terResult = tesSUCCESS;
|
|
||||||
#endif
|
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult;
|
Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult;
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText();
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText();
|
||||||
@@ -379,7 +376,7 @@ TER OfferCreateTransactor::doApply()
|
|||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText();
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText();
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID);
|
Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID);
|
||||||
Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText();
|
Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).getFullText();
|
||||||
|
|
||||||
// Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID);
|
// Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID);
|
||||||
// Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID);
|
// Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID);
|
||||||
@@ -387,7 +384,7 @@ TER OfferCreateTransactor::doApply()
|
|||||||
if (tesSUCCESS == terResult
|
if (tesSUCCESS == terResult
|
||||||
&& saTakerPays // Still wanting something.
|
&& saTakerPays // Still wanting something.
|
||||||
&& saTakerGets // Still offering something.
|
&& saTakerGets // Still offering something.
|
||||||
&& mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded.
|
&& mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive()) // Still funded.
|
||||||
{
|
{
|
||||||
// We need to place the remainder of the offer into its order book.
|
// We need to place the remainder of the offer into its order book.
|
||||||
Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s")
|
Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ buster.testRunner.timeout = 5000;
|
|||||||
|
|
||||||
buster.testCase("Offer tests", {
|
buster.testCase("Offer tests", {
|
||||||
'setUp' : testutils.build_setup(),
|
'setUp' : testutils.build_setup(),
|
||||||
// 'setUp' : testutils.build_setup({ verbose: true }),
|
// 'setUp' : testutils.build_setup({ verbose: true, standalone: false }),
|
||||||
'tearDown' : testutils.build_teardown(),
|
'tearDown' : testutils.build_teardown(),
|
||||||
|
|
||||||
"offer create then cancel in one ledger" :
|
"offer create then cancel in one ledger" :
|
||||||
@@ -451,6 +451,90 @@ buster.testCase("Offer tests", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"//=>ripple currency conversion : offerer into debt" :
|
||||||
|
// alice in, carol out
|
||||||
|
function (done) {
|
||||||
|
var self = this;
|
||||||
|
var seq;
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Create accounts.";
|
||||||
|
|
||||||
|
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Set limits.";
|
||||||
|
|
||||||
|
testutils.credit_limits(self.remote,
|
||||||
|
{
|
||||||
|
"alice" : "2000/EUR/carol",
|
||||||
|
"bob" : "100/USD/alice",
|
||||||
|
"carol" : "1000/EUR/bob"
|
||||||
|
},
|
||||||
|
callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Create offer to exchange.";
|
||||||
|
|
||||||
|
self.remote.transaction()
|
||||||
|
.offer_create("bob", "50/USD/alice", "200/EUR/carol")
|
||||||
|
.on('proposed', function (m) {
|
||||||
|
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
|
||||||
|
callback(m.result !== 'tesSUCCESS');
|
||||||
|
|
||||||
|
seq = m.tx_json.Sequence;
|
||||||
|
})
|
||||||
|
.submit();
|
||||||
|
},
|
||||||
|
// function (callback) {
|
||||||
|
// self.what = "Alice converts USD to EUR via ripple.";
|
||||||
|
//
|
||||||
|
// self.remote.transaction()
|
||||||
|
// .payment("alice", "alice", "10/EUR/carol")
|
||||||
|
// .send_max("50/USD/alice")
|
||||||
|
// .on('proposed', function (m) {
|
||||||
|
// // console.log("proposed: %s", JSON.stringify(m));
|
||||||
|
//
|
||||||
|
// callback(m.result !== 'tesSUCCESS');
|
||||||
|
// })
|
||||||
|
// .submit();
|
||||||
|
// },
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Alice converts USD to EUR via offer.";
|
||||||
|
|
||||||
|
self.remote.transaction()
|
||||||
|
.offer_create("alice", "200/EUR/carol", "50/USD/alice")
|
||||||
|
.on('proposed', function (m) {
|
||||||
|
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
|
||||||
|
callback(m.result !== 'tesSUCCESS');
|
||||||
|
|
||||||
|
seq = m.tx_json.Sequence;
|
||||||
|
})
|
||||||
|
.submit();
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Verify balances.";
|
||||||
|
|
||||||
|
testutils.verify_balances(self.remote,
|
||||||
|
{
|
||||||
|
"alice" : [ "-50/USD/bob", "200/EUR/carol" ],
|
||||||
|
"bob" : [ "50/USD/alice", "-200/EUR/carol" ],
|
||||||
|
"carol" : [ "-200/EUR/alice", "200/EUR/bob" ],
|
||||||
|
},
|
||||||
|
callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Verify offer consumed.";
|
||||||
|
|
||||||
|
testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
|
||||||
|
},
|
||||||
|
], function (error) {
|
||||||
|
buster.refute(error, self.what);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
"ripple currency conversion : in parts" :
|
"ripple currency conversion : in parts" :
|
||||||
function (done) {
|
function (done) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|||||||
Reference in New Issue
Block a user