diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 50a246e641..58109b3a9c 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -1859,6 +1859,10 @@
+
+ True
+ True
+
True
True
@@ -3435,6 +3439,8 @@
+
+
@@ -3457,6 +3463,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index b338d4e32c..1d7b87c94c 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -2592,6 +2592,9 @@
ripple\app\tx\tests
+
+ ripple\app\tx\tests
+
ripple\app\tx\tests
@@ -4194,6 +4197,9 @@
ripple\test\jtx
+
+ ripple\test\jtx
+
ripple\test\jtx
@@ -4215,6 +4221,9 @@
ripple\test\jtx\impl
+
+ ripple\test\jtx\impl
+
ripple\test\jtx\impl
diff --git a/src/BeastConfig.h b/src/BeastConfig.h
index 225ed982e0..65eaf0fc64 100644
--- a/src/BeastConfig.h
+++ b/src/BeastConfig.h
@@ -192,4 +192,11 @@
#define RIPPLE_USE_OPENSSL 0
#endif
+/** Config: RIPPLE_ENABLE_DELIVERMIN
+ Enables processing of delivermin in transactions
+*/
+#ifndef RIPPLE_ENABLE_DELIVERMIN
+#define RIPPLE_ENABLE_DELIVERMIN 0
+#endif
+
#endif
diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp
index b4f934e42c..d355c3f865 100644
--- a/src/ripple/app/tx/impl/Payment.cpp
+++ b/src/ripple/app/tx/impl/Payment.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
namespace ripple {
@@ -44,6 +45,12 @@ Payment::preCheck ()
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
+ bool const deliverMin =
+#if RIPPLE_ENABLE_DELIVERMIN
+#else
+ (view().flags() & tapENABLE_TESTING) &&
+#endif
+ mTxn.isFieldPresent(sfDeliverMin);
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
@@ -138,6 +145,45 @@ Payment::preCheck ()
"No ripple direct specified for XRP to XRP.";
return temBAD_SEND_XRP_NO_DIRECT;
}
+ if (deliverMin)
+ {
+ #if ! RIPPLE_ENABLE_DELIVERMIN
+ if (! (view().flags() & tapENABLE_TESTING))
+ return temMALFORMED;
+ #endif
+
+ if (! partialPaymentAllowed)
+ {
+ j_.trace << "Malformed transaction: Partial payment not "
+ "specified for " << jss::DeliverMin.c_str() << ".";
+ return temBAD_AMOUNT;
+ }
+
+ auto const dMin = mTxn.getFieldAmount(sfDeliverMin);
+ if (!isLegalNet(dMin) || dMin <= zero)
+ {
+ j_.trace << "Malformed transaction: Invalid " <<
+ jss::DeliverMin.c_str() << " amount. " <<
+ dMin.getFullText();
+ return temBAD_AMOUNT;
+ }
+ if (dMin.issue() != saDstAmount.issue())
+ {
+ j_.trace << "Malformed transaction: Dst issue differs "
+ "from " << jss::DeliverMin.c_str() << ". " <<
+ dMin.getFullText();
+ return temBAD_AMOUNT;
+ }
+ if (bMax &&
+ (dMin.getCurrency() == maxSourceAmount.getCurrency() &&
+ dMin > maxSourceAmount))
+ {
+ j_.trace << "Malformed transaction: SendMax less than " <<
+ jss::DeliverMin.c_str() << ". " <<
+ dMin.getFullText();
+ return temBAD_AMOUNT;
+ }
+ }
return Transactor::preCheck ();
}
@@ -152,6 +198,13 @@ Payment::doApply ()
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
+ bool const deliverMin =
+#if RIPPLE_ENABLE_DELIVERMIN
+#else
+ (view().flags() & tapENABLE_TESTING) &&
+#endif
+ mTxn.isFieldPresent(sfDeliverMin);
+
AccountID const uDstAccountID (mTxn.getAccountID (sfDestination));
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
@@ -291,8 +344,15 @@ Payment::doApply ()
// TODO: is this right? If the amount is the correct amount, was
// the delivered amount previously set?
- if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount)
- ctx_.deliverAmount (rc.actualAmountOut);
+ if (rc.result () == tesSUCCESS &&
+ rc.actualAmountOut != saDstAmount)
+ {
+ if (deliverMin && rc.actualAmountOut <
+ mTxn.getFieldAmount (sfDeliverMin))
+ rc.setResult (tecPATH_PARTIAL);
+ else
+ ctx_.deliverAmount (rc.actualAmountOut);
+ }
terResult = rc.result ();
}
diff --git a/src/ripple/app/tx/tests/DeliverMin.test.cpp b/src/ripple/app/tx/tests/DeliverMin.test.cpp
new file mode 100644
index 0000000000..f4764a089c
--- /dev/null
+++ b/src/ripple/app/tx/tests/DeliverMin.test.cpp
@@ -0,0 +1,129 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2015 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class DeliverMin_test : public beast::unit_test::suite
+{
+public:
+ void
+ test_convert_all_of_an_asset()
+ {
+ testcase("Convert all of an asset using DeliverMin");
+
+ using namespace jtx;
+ auto const gw = Account("gateway");
+ auto const USD = gw["USD"];
+
+ {
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob", "carol", gw);
+ env.trust(USD(100), "alice", "bob", "carol");
+ env(pay("alice", "bob", USD(10)), delivermin(USD(10)), ter(temBAD_AMOUNT));
+ env(pay("alice", "bob", USD(10)), delivermin(USD(-5)),
+ txflags(tfPartialPayment), ter(temBAD_AMOUNT));
+ env(pay("alice", "bob", USD(10)), delivermin(XRP(5)),
+ txflags(tfPartialPayment), ter(temBAD_AMOUNT));
+ env(pay("alice", "bob", USD(10)),
+ delivermin(Account("carol")["USD"](5)),
+ txflags(tfPartialPayment), ter(temBAD_AMOUNT));
+ env(pay("alice", "bob", USD(10)), delivermin(USD(15)),
+ txflags(tfPartialPayment), ter(tecPATH_DRY));
+ env(pay("alice", "bob", USD(10)),
+ delivermin(USD(10)), txflags(tfPartialPayment),
+ sendmax(USD(9)), ter(temBAD_AMOUNT));
+ env(pay(gw, "carol", USD(50)));
+ env(offer("carol", XRP(5), USD(5)));
+ env(pay("alice", "bob", USD(10)), paths(XRP),
+ delivermin(USD(7)), txflags(tfPartialPayment),
+ sendmax(XRP(20)), ter(tecPATH_PARTIAL));
+ env.require(balance("alice", XRP(9999.99998)));
+ env.require(balance("bob", XRP(10000)));
+
+ // DeliverMin greater than destination amount
+ env(pay("alice", "bob", USD(5)), paths(XRP),
+ delivermin(USD(50)), txflags(tfPartialPayment),
+ sendmax(XRP(5)));
+ env.require(balance("bob", USD(5)));
+ }
+
+ {
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob", gw);
+ env.trust(USD(1000), "alice", "bob");
+ env(pay(gw, "bob", USD(100)));
+ env(offer("bob", XRP(100), USD(100)));
+ env(pay("alice", "alice", USD(10000)), paths(XRP),
+ delivermin(USD(100)), txflags(tfPartialPayment),
+ sendmax(XRP(100)));
+ env.require(balance("alice", USD(100)));
+ }
+
+ {
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob", "carol", gw);
+ env.trust(USD(1000), "bob", "carol");
+ env(pay(gw, "bob", USD(200)));
+ env(offer("bob", XRP(100), USD(100)));
+ env(offer("bob", XRP(1000), USD(100)));
+ env(offer("bob", XRP(10000), USD(100)));
+ env(pay("alice", "carol", USD(10000)), paths(XRP),
+ delivermin(USD(200)), txflags(tfPartialPayment),
+ sendmax(XRP(1000)), ter(tecPATH_PARTIAL));
+ env(pay("alice", "carol", USD(10000)), paths(XRP),
+ delivermin(USD(200)), txflags(tfPartialPayment),
+ sendmax(XRP(1100)));
+ env.require(balance("bob", USD(0)));
+ env.require(balance("carol", USD(200)));
+ }
+
+ {
+ Env env(*this);
+ env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw);
+ env.trust(USD(1000), "bob", "carol", "dan");
+ env(pay(gw, "bob", USD(100)));
+ env(pay(gw, "dan", USD(100)));
+ env(offer("bob", XRP(100), USD(100)));
+ env(offer("bob", XRP(1000), USD(100)));
+ env(offer("dan", XRP(100), USD(100)));
+ env(pay("alice", "carol", USD(10000)), paths(XRP),
+ delivermin(USD(200)), txflags(tfPartialPayment),
+ sendmax(XRP(200)));
+ env.require(balance("bob", USD(0)));
+ env.require(balance("carol", USD(200)));
+ env.require(balance("dan", USD(0)));
+ }
+ }
+
+ void
+ run()
+ {
+ test_convert_all_of_an_asset();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(DeliverMin,app,ripple)
+
+} // test
+} // ripple
diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h
index 8efeb2fb6a..03b8ee7d63 100644
--- a/src/ripple/protocol/JsonFields.h
+++ b/src/ripple/protocol/JsonFields.h
@@ -39,6 +39,7 @@ JSS ( Account ); // in: TransactionSign; field.
JSS ( Amount ); // in: TransactionSign; field.
JSS ( ClearFlag ); // field.
JSS ( Destination ); // in: TransactionSign; field.
+JSS ( DeliverMin ); // in: TransactionSign
JSS ( Fee ); // in/out: TransactionSign; field.
JSS ( Flags ); // in/out: TransactionSign; field.
JSS ( Invalid ); //
diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h
index 7fab19cef8..b4acabf413 100644
--- a/src/ripple/protocol/SField.h
+++ b/src/ripple/protocol/SField.h
@@ -381,6 +381,7 @@ extern SField const sfLowLimit;
extern SField const sfHighLimit;
extern SField const sfFee;
extern SField const sfSendMax;
+extern SField const sfDeliverMin;
// currency amount (uncommon)
extern SField const sfMinimumOffer;
diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp
index 5597fd15de..a8fbf97039 100644
--- a/src/ripple/protocol/impl/SField.cpp
+++ b/src/ripple/protocol/impl/SField.cpp
@@ -160,15 +160,16 @@ SField const sfTakerGetsCurrency = make::one(&sfTakerGetsCurrency, STI_HASH160,
SField const sfTakerGetsIssuer = make::one(&sfTakerGetsIssuer, STI_HASH160, 4, "TakerGetsIssuer");
// currency amount (common)
-SField const sfAmount = make::one(&sfAmount, STI_AMOUNT, 1, "Amount");
-SField const sfBalance = make::one(&sfBalance, STI_AMOUNT, 2, "Balance");
-SField const sfLimitAmount = make::one(&sfLimitAmount, STI_AMOUNT, 3, "LimitAmount");
-SField const sfTakerPays = make::one(&sfTakerPays, STI_AMOUNT, 4, "TakerPays");
-SField const sfTakerGets = make::one(&sfTakerGets, STI_AMOUNT, 5, "TakerGets");
-SField const sfLowLimit = make::one(&sfLowLimit, STI_AMOUNT, 6, "LowLimit");
-SField const sfHighLimit = make::one(&sfHighLimit, STI_AMOUNT, 7, "HighLimit");
-SField const sfFee = make::one(&sfFee, STI_AMOUNT, 8, "Fee");
-SField const sfSendMax = make::one(&sfSendMax, STI_AMOUNT, 9, "SendMax");
+SField const sfAmount = make::one(&sfAmount, STI_AMOUNT, 1, "Amount");
+SField const sfBalance = make::one(&sfBalance, STI_AMOUNT, 2, "Balance");
+SField const sfLimitAmount = make::one(&sfLimitAmount, STI_AMOUNT, 3, "LimitAmount");
+SField const sfTakerPays = make::one(&sfTakerPays, STI_AMOUNT, 4, "TakerPays");
+SField const sfTakerGets = make::one(&sfTakerGets, STI_AMOUNT, 5, "TakerGets");
+SField const sfLowLimit = make::one(&sfLowLimit, STI_AMOUNT, 6, "LowLimit");
+SField const sfHighLimit = make::one(&sfHighLimit, STI_AMOUNT, 7, "HighLimit");
+SField const sfFee = make::one(&sfFee, STI_AMOUNT, 8, "Fee");
+SField const sfSendMax = make::one(&sfSendMax, STI_AMOUNT, 9, "SendMax");
+SField const sfDeliverMin = make::one(&sfDeliverMin, STI_AMOUNT, 10, "DeliverMin");
// currency amount (uncommon)
SField const sfMinimumOffer = make::one(&sfMinimumOffer, STI_AMOUNT, 16, "MinimumOffer");
diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp
index 260e79d43e..8b5c5b11cd 100644
--- a/src/ripple/protocol/impl/TxFormats.cpp
+++ b/src/ripple/protocol/impl/TxFormats.cpp
@@ -64,6 +64,7 @@ TxFormats::TxFormats ()
<< SOElement (sfPaths, SOE_DEFAULT)
<< SOElement (sfInvoiceID, SOE_OPTIONAL)
<< SOElement (sfDestinationTag, SOE_OPTIONAL)
+ << SOElement (sfDeliverMin, SOE_OPTIONAL)
;
add ("EnableAmendment", ttAMENDMENT)
diff --git a/src/ripple/test/jtx.h b/src/ripple/test/jtx.h
index 3c48ba40ea..fdfcbbd8f8 100644
--- a/src/ripple/test/jtx.h
+++ b/src/ripple/test/jtx.h
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/src/ripple/test/jtx/amount.h b/src/ripple/test/jtx/amount.h
index 8ab4db930c..4603301780 100644
--- a/src/ripple/test/jtx/amount.h
+++ b/src/ripple/test/jtx/amount.h
@@ -172,7 +172,7 @@ struct XRP_t
std::int64_t, std::uint64_t>{v} *
dropsPerXRP::value };
}
-
+
PrettyAmount
operator()(double v) const
{
diff --git a/src/ripple/test/jtx/delivermin.h b/src/ripple/test/jtx/delivermin.h
new file mode 100644
index 0000000000..152281927c
--- /dev/null
+++ b/src/ripple/test/jtx/delivermin.h
@@ -0,0 +1,50 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012, 2013 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_TEST_JTX_DELIVERMIN_H_INCLUDED
+#define RIPPLE_TEST_JTX_DELIVERMIN_H_INCLUDED
+
+#include
+#include
+
+namespace ripple {
+namespace test {
+namespace jtx {
+
+/** Sets the DeliverMin on a JTx. */
+class delivermin
+{
+private:
+ STAmount amount_;
+
+public:
+ delivermin (STAmount const& amount)
+ : amount_(amount)
+ {
+ }
+
+ void
+ operator()(Env const&, JTx& jtx) const;
+};
+
+} // jtx
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/test/jtx/impl/amount.cpp b/src/ripple/test/jtx/impl/amount.cpp
index 270371f771..4275a4e46d 100644
--- a/src/ripple/test/jtx/impl/amount.cpp
+++ b/src/ripple/test/jtx/impl/amount.cpp
@@ -52,6 +52,24 @@ PrettyAmount::operator AnyAmount() const
return { amount_ };
}
+template
+static
+std::string
+to_places(const T d, std::uint8_t places)
+{
+ assert(places <= std::numeric_limits::digits10);
+
+ std::ostringstream oss;
+ oss << std::setprecision(places) << std::fixed << d;
+
+ std::string out = oss.str();
+ out.erase(out.find_last_not_of('0') + 1, std::string::npos);
+ if (out.back() == '.')
+ out.pop_back();
+
+ return out;
+}
+
std::ostream&
operator<< (std::ostream& os,
PrettyAmount const& amount)
@@ -72,11 +90,10 @@ operator<< (std::ostream& os,
}
auto const d = double(n) /
dropsPerXRP::value;
- os.precision(6);
if (amount.value().negative())
- os << "-" << d << " XRP";
- else
- os << d << " XRP";
+ os << "-";
+
+ os << to_places(d, 6) << " XRP";
}
else
{
diff --git a/src/ripple/test/jtx/impl/delivermin.cpp b/src/ripple/test/jtx/impl/delivermin.cpp
new file mode 100644
index 0000000000..1d105037bb
--- /dev/null
+++ b/src/ripple/test/jtx/impl/delivermin.cpp
@@ -0,0 +1,36 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012, 2013 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+namespace jtx {
+
+void
+delivermin::operator()(Env const& env, JTx& jt) const
+{
+ jt.jv[jss::DeliverMin] = amount_.getJson(0);
+}
+
+} // jtx
+} // test
+} // ripple
diff --git a/src/ripple/unity/app_tx.cpp b/src/ripple/unity/app_tx.cpp
index 5af83af0bd..5ce8a4e685 100644
--- a/src/ripple/unity/app_tx.cpp
+++ b/src/ripple/unity/app_tx.cpp
@@ -47,3 +47,4 @@
#include
#include
#include
+#include
diff --git a/src/ripple/unity/test.cpp b/src/ripple/unity/test.cpp
index 4364861c70..b9008d1a5f 100644
--- a/src/ripple/unity/test.cpp
+++ b/src/ripple/unity/test.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include