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