Add support for offers to allow parties to go into debt. Broken.

This commit is contained in:
Arthur Britto
2012-12-15 00:06:12 -08:00
parent fd0bd1433f
commit 4baa8b3c5d
4 changed files with 136 additions and 27 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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")

View File

@@ -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;