diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index da4cdac1c..cb3e963e4 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -1709,6 +1709,10 @@
True
True
+
+ True
+ True
+
@@ -1809,6 +1813,12 @@
+
+ True
+ True
+
+
+
True
True
@@ -3168,7 +3178,7 @@
True
True
-
+
True
True
@@ -3696,6 +3706,10 @@
True
True
+
+ True
+ True
+
True
True
@@ -3746,6 +3760,8 @@
+
+
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index 997ec9222..7d252272f 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -2457,6 +2457,9 @@
ripple\app\tests
+
+ ripple\app\tests
+
ripple\app\tx
@@ -2556,6 +2559,12 @@
ripple\app\tx\impl
+
+ ripple\app\tx\impl
+
+
+ ripple\app\tx\impl
+
ripple\app\tx\impl
@@ -3870,7 +3879,7 @@
ripple\rpc\handlers
-
+
ripple\rpc\handlers
@@ -4362,6 +4371,9 @@
ripple\test\jtx\impl
+
+ ripple\test\jtx\impl
+
ripple\test\jtx\impl
@@ -4425,6 +4437,9 @@
ripple\test\jtx
+
+ ripple\test\jtx
+
ripple\test\jtx
diff --git a/src/ripple/app/tests/SusPay_test.cpp b/src/ripple/app/tests/SusPay_test.cpp
new file mode 100644
index 000000000..d61354f1e
--- /dev/null
+++ b/src/ripple/app/tests/SusPay_test.cpp
@@ -0,0 +1,322 @@
+//------------------------------------------------------------------------------
+/*
+ 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
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+struct SusPay_test : public beast::unit_test::suite
+{
+ template
+ static
+ uint256
+ digest (Args&&... args)
+ {
+ sha256_hasher h;
+ using beast::hash_append;
+ hash_append(h, args...);
+ auto const d = static_cast<
+ sha256_hasher::result_type>(h);
+ uint256 result;
+ std::memcpy(result.data(), d.data(), d.size());
+ return result;
+ }
+
+ // Create condition
+ // First is digest, second is pre-image
+ static
+ std::pair
+ cond (std::string const& receipt)
+ {
+ std::pair result;
+ result.second = digest(receipt);
+ result.first = digest(result.second);
+ return result;
+ }
+
+ static
+ Json::Value
+ condpay (jtx::Account const& account, jtx::Account const& to,
+ STAmount const& amount, uint256 const& digest,
+ NetClock::time_point const& expiry)
+ {
+ using namespace jtx;
+ Json::Value jv;
+ jv[jss::TransactionType] = "SuspendedPaymentCreate";
+ jv[jss::Flags] = tfUniversal;
+ jv[jss::Account] = account.human();
+ jv[jss::Destination] = to.human();
+ jv[jss::Amount] = amount.getJson(0);
+ jv["CancelAfter"] =
+ expiry.time_since_epoch().count();
+ jv["Digest"] = to_string(digest);
+ return jv;
+ }
+
+ static
+ Json::Value
+ lockup (jtx::Account const& account, jtx::Account const& to,
+ STAmount const& amount, NetClock::time_point const& expiry)
+ {
+ using namespace jtx;
+ Json::Value jv;
+ jv[jss::TransactionType] = "SuspendedPaymentCreate";
+ jv[jss::Flags] = tfUniversal;
+ jv[jss::Account] = account.human();
+ jv[jss::Destination] = to.human();
+ jv[jss::Amount] = amount.getJson(0);
+ jv["FinishAfter"] =
+ expiry.time_since_epoch().count();
+ return jv;
+ }
+
+ static
+ Json::Value
+ finish (jtx::Account const& account,
+ jtx::Account const& from, std::uint32_t seq)
+ {
+ Json::Value jv;
+ jv[jss::TransactionType] = "SuspendedPaymentFinish";
+ jv[jss::Flags] = tfUniversal;
+ jv[jss::Account] = account.human();
+ jv["Owner"] = from.human();
+ jv["OfferSequence"] = seq;
+ return jv;
+ }
+
+ static
+ Json::Value
+ finish (jtx::Account const& account,
+ jtx::Account const& from, std::uint32_t seq,
+ uint256 const& digest, uint256 const& preimage)
+ {
+ Json::Value jv;
+ jv[jss::TransactionType] = "SuspendedPaymentFinish";
+ jv[jss::Flags] = tfUniversal;
+ jv[jss::Account] = account.human();
+ jv["Owner"] = from.human();
+ jv["OfferSequence"] = seq;
+ jv["Method"] = 1;
+ jv["Digest"] = to_string(digest);
+ jv["Proof"] = to_string(preimage);
+ return jv;
+ }
+
+ static
+ Json::Value
+ cancel (jtx::Account const& account,
+ jtx::Account const& from, std::uint32_t seq)
+ {
+ Json::Value jv;
+ jv[jss::TransactionType] = "SuspendedPaymentCancel";
+ jv[jss::Flags] = tfUniversal;
+ jv[jss::Account] = account.human();
+ jv["Owner"] = from.human();
+ jv["OfferSequence"] = seq;
+ return jv;
+ }
+
+ void
+ testEnablement()
+ {
+ using namespace jtx;
+ using namespace std::chrono;
+ using S = seconds;
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob");
+ auto const c = cond("receipt");
+ // syntax
+ env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
+ env.disable_testing();
+ // disabled in production
+ env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
+ env(finish("bob", "alice", 1), ter(temDISABLED));
+ env(cancel("bob", "alice", 1), ter(temDISABLED));
+ }
+ }
+
+ void
+ testTags()
+ {
+ using namespace jtx;
+ using namespace std::chrono;
+ using S = seconds;
+ {
+ Env env(*this);
+ auto const alice = Account("alice");
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), alice, "bob");
+ auto const c = cond("receipt");
+ auto const seq = env.seq(alice);
+ // set source and dest tags
+ env(condpay(alice, "bob", XRP(1000), c.first, T(S{1})), stag(1), dtag(2));
+ auto const sle = env.le(keylet::susPay(alice.id(), seq));
+ expect((*sle)[sfSourceTag] == 1);
+ expect((*sle)[sfDestinationTag] == 2);
+ }
+ }
+
+ void
+ testFails()
+ {
+ using namespace jtx;
+ using namespace std::chrono;
+ using S = seconds;
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob");
+ auto const c = cond("receipt");
+ // VFALCO Should we enforce this?
+ // expiration in the past
+ //env(condpay("alice", "bob", XRP(1000), c.first, T(S{-1})), ter(tecNO_PERMISSION));
+ // expiration beyond the limit
+ env(condpay("alice", "bob", XRP(1000), c.first, T(days(7+1))), ter(tecNO_PERMISSION));
+ // no destination account
+ env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
+ env.fund(XRP(5000), "carol");
+ env(condpay("alice", "carol",
+ XRP(1000), c.first, T(S{1})), stag(2));
+ env(condpay("alice", "carol",
+ XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
+ env(fset("carol", asfRequireDest));
+ // missing destination tag
+ env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
+ env(condpay("alice", "carol",
+ XRP(1000), c.first, T(S{1})), dtag(1));
+ }
+ }
+
+ void
+ testLockup()
+ {
+ using namespace jtx;
+ using namespace std::chrono;
+ using S = seconds;
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob");
+ auto const seq = env.seq("alice");
+ env(lockup("alice", "alice", XRP(1000), T(S{1})));
+ env.require(balance("alice", XRP(4000) - drops(10)));
+ env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
+ env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
+ env.close();
+ env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
+ env(finish("bob", "alice", seq));
+ }
+ }
+
+ void
+ testCondPay()
+ {
+ using namespace jtx;
+ using namespace std::chrono;
+ using S = seconds;
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob", "carol");
+ auto const c = cond("receipt");
+ auto const seq = env.seq("alice");
+ expect((*env.le("alice"))[sfOwnerCount] == 0);
+ env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ env.require(balance("alice", XRP(4000) - drops(10)));
+ env.require(balance("carol", XRP(5000)));
+ env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ env(finish("bob", "alice", seq, c.first, c.first), ter(temBAD_SIGNATURE));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ env(finish("bob", "alice", seq, c.first, c.second));
+ // SLE removed on finish
+ expect(! env.le(keylet::susPay(Account("alice").id(), seq)));
+ expect((*env.le("alice"))[sfOwnerCount] == 0);
+ env.require(balance("carol", XRP(6000)));
+ env(cancel("bob", "alice", seq), ter(tecNO_TARGET));
+ expect((*env.le("alice"))[sfOwnerCount] == 0);
+ env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
+ env.close();
+ }
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob", "carol");
+ auto const c = cond("receipt");
+ auto const seq = env.seq("alice");
+ expect((*env.le("alice"))[sfOwnerCount] == 0);
+ env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
+ env.close();
+ env.require(balance("alice", XRP(4000) - drops(10)));
+ // balance restored on cancel
+ env(cancel("bob", "alice", seq));
+ env.require(balance("alice", XRP(5000) - drops(10)));
+ // SLE removed on cancel
+ expect(! env.le(keylet::susPay(Account("alice").id(), seq)));
+ }
+ {
+ Env env(*this);
+ auto const T = [&env](NetClock::duration const& d)
+ { return env.clock.now() + d; };
+ env.fund(XRP(5000), "alice", "bob", "carol");
+ env.close();
+ auto const c = cond("receipt");
+ auto const seq = env.seq("alice");
+ env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ // cancel fails before expiration
+ env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ env.close();
+ // finish fails after expiration
+ env(finish("bob", "alice", seq, c.first, c.second), ter(tecNO_PERMISSION));
+ expect((*env.le("alice"))[sfOwnerCount] == 1);
+ env.require(balance("carol", XRP(5000)));
+ }
+ }
+
+ void run() override
+ {
+ testEnablement();
+ testTags();
+ testFails();
+ testLockup();
+ testCondPay();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(SusPay,app,ripple);
+
+} // test
+} // ripple
diff --git a/src/ripple/app/tx/impl/SusPay.cpp b/src/ripple/app/tx/impl/SusPay.cpp
new file mode 100644
index 000000000..7db0263cb
--- /dev/null
+++ b/src/ripple/app/tx/impl/SusPay.cpp
@@ -0,0 +1,410 @@
+//------------------------------------------------------------------------------
+/*
+ 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+
+/*
+ SuspendedPayment
+
+ A suspended payment ("SusPay") sequesters XRP in its
+ own ledger entry until a SusPayFinish or a SusPayCancel
+ transaction mentioning the ledger entry is successfully
+ applied to the ledger. If the SusPayFinish succeeds,
+ the destination account (which must exist) receives the
+ XRP. If the SusPayCancel succeeds, the account which
+ created the SusPay is credited the XRP.
+
+ SusPayCreate
+
+ When the SusPay is created, an optional condition may
+ be attached. The condition is specified by providing
+ the cryptographic digest of the condition to be met.
+
+ At the time of creation, one or both of the fields
+ sfCancelAfter and sfFinishAfter may be provided. If
+ neither field is specified, the transaction is
+ malformed.
+
+ Since the SusPay eventually becomes a payment, an
+ optional DestinationTag and an optional SourceTag
+ is supported in the SusPayCreate transaction.
+
+ Validation rules:
+
+ sfDigest
+ If present, proof is required on a SusPayFinish.
+
+ sfCancelAfter
+ If present, SusPay may be canceled after the
+ specified time (seconds after the Ripple epoch).
+
+ sfFinishAfter
+ If present, must be prior to sfCancelAfter.
+ A SusPayFinish succeeds only in ledgers after
+ sfFinishAfter but before sfCancelAfter.
+
+ If absent, same as parentCloseTime
+
+ Malformed if both sfCancelAfter, sfFinishAfter
+ are absent.
+
+ Malformed if both sfFinishAfter, sfCancelAfter
+ specified and sfCancelAfter <= sfFinishAfter
+
+ SusPayFinish
+
+ Any account may submit a SusPayFinish. If the SusPay
+ ledger entry specifies a condition, the SusPayFinish
+ must provide the sfMethod, original sfDigest, and
+ sfProof fields. Depending on the method, a
+ cryptographic operation will be performed on sfProof
+ and the result must match the sfDigest or else the
+ SusPayFinish is considered as having an invalid
+ signature.
+
+ Only sfMethod==1 is supported, where sfProof must be a
+ 256-bit unsigned big-endian integer which when hashed
+ using SHA256 produces digest == sfDigest.
+
+ If the SusPay ledger entry specifies sfFinishAfter, the
+ transaction will fail if parentCloseTime <= sfFinishAfter.
+
+ SusPayFinish transactions must be submitted before a
+ SusPay's sfCancelAfter if present.
+
+ If the SusPay ledger entry specifies sfCancelAfter, the
+ transaction will fail if sfCancelAfter <= parentCloseTime.
+
+ NOTE: It must always be possible to verify the condition
+ without retrieving the SusPay ledger entry.
+
+ SusPayCancel
+
+ Any account may submit a SusPayCancel transaction.
+
+ If the SusPay ledger entry does not specify a
+ sfCancelAfter, the cancel transaction will fail.
+
+ If parentCloseTime <= sfCancelAfter, the transaction
+ will fail.
+
+ When a SusPay is canceled, the funds are returned to
+ the source account.
+
+ By careful selection of fields in each transaction,
+ these operations may be achieved:
+
+ * Lock up XRP for a time period
+ * Execute a payment conditionally
+*/
+
+//------------------------------------------------------------------------------
+
+TER
+SusPayCreate::preflight (PreflightContext const& ctx)
+{
+ if (! (ctx.flags & tapENABLE_TESTING) &&
+ ! ctx.rules.enabled(featureSusPay,
+ ctx.config.features))
+ return temDISABLED;
+
+ if (! isXRP(ctx.tx[sfAmount]))
+ return temBAD_AMOUNT;
+
+ if (ctx.tx[sfAmount] <= beast::zero)
+ return temBAD_AMOUNT;
+
+ if (! ctx.tx[~sfCancelAfter] &&
+ ! ctx.tx[~sfFinishAfter])
+ return temBAD_EXPIRATION;
+
+ if (ctx.tx[~sfCancelAfter] && ctx.tx[~sfFinishAfter] &&
+ ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
+ return temBAD_EXPIRATION;
+
+ return Transactor::preflight(ctx);
+}
+
+TER
+SusPayCreate::doApply()
+{
+ // For now, require that all operations can be
+ // canceled, or finished without proof, within a
+ // reasonable period of time for the first release.
+ using namespace std::chrono;
+ auto const maxExpire =
+ ctx_.view().info().parentCloseTime +
+ seconds{days(7)}.count();
+ if (ctx_.tx[~sfDigest])
+ {
+ if (! ctx_.tx[~sfCancelAfter] ||
+ maxExpire <= ctx_.tx[sfCancelAfter])
+ return tecNO_PERMISSION;
+ }
+ else
+ {
+ if (ctx_.tx[~sfCancelAfter] &&
+ maxExpire <= ctx_.tx[sfCancelAfter])
+ return tecNO_PERMISSION;
+ if (ctx_.tx[~sfFinishAfter] &&
+ maxExpire <= ctx_.tx[sfFinishAfter])
+ return tecNO_PERMISSION;
+ }
+
+ XRPAmount const amount =
+ STAmount(ctx_.tx[sfAmount]).mantissa();
+
+ auto const account = ctx_.tx[sfAccount];
+
+ auto const sle = ctx_.view().peek(
+ keylet::account(account));
+
+ if (XRPAmount(STAmount((*sle)[sfBalance]).mantissa()) <
+ XRPAmount(ctx_.view().fees().accountReserve(
+ (*sle)[sfOwnerCount] + 1)))
+ return tecINSUFFICIENT_RESERVE;
+
+ if (XRPAmount(STAmount((*sle)[sfBalance]).mantissa()) <
+ amount + XRPAmount(ctx_.view().fees().accountReserve(
+ (*sle)[sfOwnerCount] + 1)))
+ return tecUNFUNDED;
+
+ // Check destination account
+ {
+ auto const sled = ctx_.view().read(
+ keylet::account(ctx_.tx[sfDestination]));
+ if (! sled)
+ return tecNO_DST;
+ if (((*sled)[sfFlags] & lsfRequireDestTag) &&
+ ! ctx_.tx[~sfDestinationTag])
+ return tecDST_TAG_NEEDED;
+ if ((*sled)[sfFlags] & lsfDisallowXRP)
+ return tecNO_TARGET;
+ }
+
+ // Create SusPay in ledger
+ auto const slep = std::make_shared(
+ keylet::susPay(account, (*sle)[sfSequence] - 1));
+ (*slep)[sfAmount] = ctx_.tx[sfAmount];
+ (*slep)[sfAccount] = account;
+ (*slep)[~sfDigest] = ctx_.tx[~sfDigest];
+ (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
+ (*slep)[sfDestination] = ctx_.tx[sfDestination];
+ (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
+ (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
+ (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
+
+ ctx_.view().insert(slep);
+
+ // Add SusPay to owner directory
+ {
+ uint64_t page;
+ TER ter = dirAdd(ctx_.view(), page,
+ keylet::ownerDir(account).key,
+ slep->key(), describeOwnerDir(account));
+ if (! isTesSuccess(ter))
+ return ter;
+ (*slep)[sfOwnerNode] = page;
+ }
+
+ // Deduct owner's balance, increment owner count
+ (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
+ (*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] + 1;
+ ctx_.view().update(sle);
+
+ return tesSUCCESS;
+}
+
+//------------------------------------------------------------------------------
+
+TER
+SusPayFinish::preflight (PreflightContext const& ctx)
+{
+ if (! (ctx.flags & tapENABLE_TESTING) &&
+ ! ctx.rules.enabled(featureSusPay,
+ ctx.config.features))
+ return temDISABLED;
+
+ if (ctx.tx[~sfMethod])
+ {
+ // Condition
+ switch(ctx.tx[sfMethod])
+ {
+ case 1:
+ {
+ if (! ctx.tx[~sfDigest])
+ return temMALFORMED;
+ if (! ctx.tx[~sfProof])
+ return temMALFORMED;
+ sha256_hasher h;
+ using beast::hash_append;
+ hash_append(h, ctx.tx[sfProof]);
+ uint256 digest;
+ {
+ auto const result = static_cast<
+ sha256_hasher::result_type>(h);
+ std::memcpy(digest.data(),
+ result.data(), result.size());
+ }
+ if (digest != ctx.tx[sfDigest])
+ return temBAD_SIGNATURE;
+ break;
+ }
+ default:
+ return temMALFORMED;
+ }
+ }
+ else
+ {
+ // No Condition
+ if (ctx.tx[~sfDigest])
+ return temMALFORMED;
+ if (ctx.tx[~sfProof])
+ return temMALFORMED;
+ }
+
+ return Transactor::preflight(ctx);
+}
+
+TER
+SusPayFinish::doApply()
+{
+ // peek SusPay SLE
+ auto const k = keylet::susPay(
+ ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
+ auto const slep = ctx_.view().peek(k);
+ if (! slep)
+ return tecNO_TARGET;
+
+ // Too soon?
+ if ((*slep)[~sfFinishAfter] &&
+ ctx_.view().info().parentCloseTime <=
+ (*slep)[sfFinishAfter])
+ return tecNO_PERMISSION;
+
+ // Too late?
+ if ((*slep)[~sfCancelAfter] &&
+ (*slep)[sfCancelAfter] <=
+ ctx_.view().info().parentCloseTime)
+ return tecNO_PERMISSION;
+
+ AccountID const account = (*slep)[sfAccount];
+
+ // Remove SusPay from owner directory
+ {
+ auto const page = (*slep)[sfOwnerNode];
+ TER const ter = dirDelete(ctx_.view(), true,
+ page, keylet::ownerDir(account).key,
+ k.key, false, page == 0);
+ if (! isTesSuccess(ter))
+ return ter;
+ }
+
+ // NOTE: These payments cannot be used to fund accounts
+
+ // Fetch Destination SLE
+ auto const sled = ctx_.view().peek(
+ keylet::account((*slep)[sfDestination]));
+ if (! sled)
+ return tecNO_DST;
+
+ // Transfer amount to destination
+ (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
+ ctx_.view().update(sled);
+
+ // Adjust source owner count
+ auto const sle = ctx_.view().peek(
+ keylet::account(account));
+ (*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] - 1;
+ ctx_.view().update(sle);
+
+ // Remove SusPay from ledger
+ ctx_.view().erase(slep);
+
+ return tesSUCCESS;
+}
+
+//------------------------------------------------------------------------------
+
+TER
+SusPayCancel::preflight (PreflightContext const& ctx)
+{
+ if (! (ctx.flags & tapENABLE_TESTING) &&
+ ! ctx.rules.enabled(featureSusPay,
+ ctx.config.features))
+ return temDISABLED;
+
+ return Transactor::preflight(ctx);
+}
+
+TER
+SusPayCancel::doApply()
+{
+ // peek SusPay SLE
+ auto const k = keylet::susPay(
+ ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
+ auto const slep = ctx_.view().peek(k);
+ if (! slep)
+ return tecNO_TARGET;
+
+ // Too soon?
+ if (! (*slep)[~sfCancelAfter] ||
+ ctx_.view().info().parentCloseTime <=
+ (*slep)[sfCancelAfter])
+ return tecNO_PERMISSION;
+
+ AccountID const account = (*slep)[sfAccount];
+
+ // Remove SusPay from owner directory
+ {
+ auto const page = (*slep)[sfOwnerNode];
+ TER const ter = dirDelete(ctx_.view(), true,
+ page, keylet::ownerDir(account).key,
+ k.key, false, page == 0);
+ if (! isTesSuccess(ter))
+ return ter;
+ }
+
+ // Transfer amount back to owner, decrement owner count
+ auto const sle = ctx_.view().peek(
+ keylet::account(account));
+ (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
+ (*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] - 1;
+ ctx_.view().update(sle);
+
+ // Remove SusPay from ledger
+ ctx_.view().erase(slep);
+
+ return tesSUCCESS;
+}
+
+} // ripple
+
diff --git a/src/ripple/app/tx/impl/SusPay.h b/src/ripple/app/tx/impl/SusPay.h
new file mode 100644
index 000000000..2263990c6
--- /dev/null
+++ b/src/ripple/app/tx/impl/SusPay.h
@@ -0,0 +1,87 @@
+//------------------------------------------------------------------------------
+/*
+ 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_TX_SUSPAY_H_INCLUDED
+#define RIPPLE_TX_SUSPAY_H_INCLUDED
+
+#include
+
+namespace ripple {
+
+class SusPayCreate
+ : public Transactor
+{
+public:
+ explicit
+ SusPayCreate (ApplyContext& ctx)
+ : Transactor(ctx)
+ {
+ }
+
+ static
+ TER
+ preflight (PreflightContext const& ctx);
+
+ TER
+ doApply() override;
+};
+
+//------------------------------------------------------------------------------
+
+class SusPayFinish
+ : public Transactor
+{
+public:
+ explicit
+ SusPayFinish (ApplyContext& ctx)
+ : Transactor(ctx)
+ {
+ }
+
+ static
+ TER
+ preflight (PreflightContext const& ctx);
+
+ TER
+ doApply() override;
+};
+
+//------------------------------------------------------------------------------
+
+class SusPayCancel
+ : public Transactor
+{
+public:
+ explicit
+ SusPayCancel (ApplyContext& ctx)
+ : Transactor(ctx)
+ {
+ }
+
+ static
+ TER
+ preflight (PreflightContext const& ctx);
+
+ TER
+ doApply() override;
+};
+
+} // ripple
+
+#endif
diff --git a/src/ripple/app/tx/impl/apply.cpp b/src/ripple/app/tx/impl/apply.cpp
index dddbf14f2..c350cbd91 100644
--- a/src/ripple/app/tx/impl/apply.cpp
+++ b/src/ripple/app/tx/impl/apply.cpp
@@ -30,6 +30,7 @@
#include
#include
#include
+#include
namespace ripple {
@@ -43,6 +44,9 @@ invoke_preflight (PreflightContext const& ctx)
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
case ttPAYMENT: return Payment ::preflight(ctx);
+ case ttSUSPAY_CREATE: return SusPayCreate ::preflight(ctx);
+ case ttSUSPAY_FINISH: return SusPayFinish ::preflight(ctx);
+ case ttSUSPAY_CANCEL: return SusPayCancel ::preflight(ctx);
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
@@ -65,6 +69,9 @@ invoke_apply (ApplyContext& ctx)
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
case ttPAYMENT: { Payment p(ctx); return p(); }
+ case ttSUSPAY_CREATE: { SusPayCreate p(ctx); return p(); }
+ case ttSUSPAY_FINISH: { SusPayFinish p(ctx); return p(); }
+ case ttSUSPAY_CANCEL: { SusPayCancel p(ctx); return p(); }
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h
index ec91f7c91..3519bd932 100644
--- a/src/ripple/protocol/Feature.h
+++ b/src/ripple/protocol/Feature.h
@@ -34,6 +34,8 @@ uint256
feature (const char* name);
/** @} */
+extern uint256 const featureSusPay;
+
} // ripple
#endif
diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h
index 9951da794..01140148c 100644
--- a/src/ripple/protocol/Indexes.h
+++ b/src/ripple/protocol/Indexes.h
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -234,6 +235,10 @@ Keylet page (uint256 const& key)
return { ltDIR_NODE, key };
}
+/** A SuspendedPayment */
+Keylet
+susPay (AccountID const& source, std::uint32_t seq);
+
} // keylet
}
diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h
index 1ee4a0ba3..49e57e612 100644
--- a/src/ripple/protocol/LedgerFormats.h
+++ b/src/ripple/protocol/LedgerFormats.h
@@ -64,15 +64,12 @@ enum LedgerEntryType
*/
ltDIR_NODE = 'd',
- /** Describes a trust line.
- */
ltRIPPLE_STATE = 'r',
ltTICKET = 'T',
ltSIGNER_LIST = 'S',
- /* Deprecated. */
ltOFFER = 'o',
ltLEDGER_HASHES = 'h',
@@ -81,6 +78,8 @@ enum LedgerEntryType
ltFEE_SETTINGS = 's',
+ ltSUSPAY = 'u',
+
// No longer used or supported. Left here to prevent accidental
// reassignment of the ledger type.
ltNICKNAME = 'n',
@@ -103,6 +102,7 @@ enum LedgerNameSpace
spaceBookDir = 'B', // Directory of order books.
spaceContract = 'c',
spaceSkipList = 's',
+ spaceSusPay = 'u',
spaceAmendment = 'f',
spaceFee = 'e',
spaceTicket = 'T',
diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h
index 154517778..d962d32fc 100644
--- a/src/ripple/protocol/SField.h
+++ b/src/ripple/protocol/SField.h
@@ -344,7 +344,7 @@ extern SField const sfMetadata;
// 8-bit integers
extern SF_U8 const sfCloseResolution;
-extern SF_U8 const sfTemplateEntryType;
+extern SF_U8 const sfMethod;
extern SF_U8 const sfTransactionResult;
// 16-bit integers
@@ -388,6 +388,8 @@ extern SF_U32 const sfReserveIncrement;
extern SF_U32 const sfSetFlag;
extern SF_U32 const sfClearFlag;
extern SF_U32 const sfSignerQuorum;
+extern SF_U32 const sfCancelAfter;
+extern SF_U32 const sfFinishAfter;
// 64-bit integers
extern SF_U64 const sfIndexNext;
@@ -461,7 +463,7 @@ extern SF_Blob const sfMemoFormat;
// variable length (uncommon)
extern SF_Blob const sfMultiSignature;
-extern SF_Blob const sfInnerSig;
+extern SF_Blob const sfProof;
// account
extern SF_Account const sfAccount;
diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h
index 12ae39b96..74dccc0f4 100644
--- a/src/ripple/protocol/TxFormats.h
+++ b/src/ripple/protocol/TxFormats.h
@@ -35,10 +35,10 @@ enum TxType
ttINVALID = -1,
ttPAYMENT = 0,
- ttCLAIM = 1, // open
- ttWALLET_ADD = 2, // unused
+ ttSUSPAY_CREATE = 1,
+ ttSUSPAY_FINISH = 2,
ttACCOUNT_SET = 3,
- ttPASSWORD_FUND = 4, // open
+ ttSUSPAY_CANCEL = 4,
ttREGULAR_KEY_SET = 5,
ttNICKNAME_SET = 6, // open
ttOFFER_CREATE = 7,
diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp
index bb58be4b8..5825de38b 100644
--- a/src/ripple/protocol/impl/Feature.cpp
+++ b/src/ripple/protocol/impl/Feature.cpp
@@ -45,4 +45,6 @@ feature (const char* name)
return feature(name, std::strlen(name));
}
+uint256 const featureSusPay = feature("SusPay");
+
} // ripple
diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp
index 4f6342176..9987d0799 100644
--- a/src/ripple/protocol/impl/Indexes.cpp
+++ b/src/ripple/protocol/impl/Indexes.cpp
@@ -308,6 +308,17 @@ Keylet page(Keylet const& root,
return page(root.key, index);
}
+Keylet
+susPay (AccountID const& source, std::uint32_t seq)
+{
+ sha512_half_hasher h;
+ using beast::hash_append;
+ hash_append(h, spaceSusPay);
+ hash_append(h, source);
+ hash_append(h, seq);
+ return { ltSUSPAY, static_cast(h) };
+}
+
} // keylet
} // ripple
diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp
index 225ae9fef..54b19d65d 100644
--- a/src/ripple/protocol/impl/LedgerFormats.cpp
+++ b/src/ripple/protocol/impl/LedgerFormats.cpp
@@ -81,6 +81,19 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfHighQualityOut, SOE_OPTIONAL)
;
+ add ("SusPay", ltSUSPAY) <<
+ SOElement (sfAccount, SOE_REQUIRED) <<
+ SOElement (sfDestination, SOE_REQUIRED) <<
+ SOElement (sfAmount, SOE_REQUIRED) <<
+ SOElement (sfDigest, SOE_OPTIONAL) <<
+ SOElement (sfCancelAfter, SOE_OPTIONAL) <<
+ SOElement (sfFinishAfter, SOE_OPTIONAL) <<
+ SOElement (sfSourceTag, SOE_OPTIONAL) <<
+ SOElement (sfDestinationTag, SOE_OPTIONAL) <<
+ SOElement (sfOwnerNode, SOE_REQUIRED) <<
+ SOElement (sfPreviousTxnID, SOE_REQUIRED) <<
+ SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED);
+
add ("LedgerHashes", ltLEDGER_HASHES)
<< SOElement (sfFirstLedgerSequence, SOE_OPTIONAL) // Remove if we do a ledger restart
<< SOElement (sfLastLedgerSequence, SOE_OPTIONAL)
diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp
index 27332a324..44fdc86cd 100644
--- a/src/ripple/protocol/impl/SField.cpp
+++ b/src/ripple/protocol/impl/SField.cpp
@@ -77,7 +77,7 @@ SField const sfIndex = make::one(&sfIndex, STI_HASH256, 258, "in
// 8-bit integers
SF_U8 const sfCloseResolution = make::one(&sfCloseResolution, STI_UINT8, 1, "CloseResolution");
-SF_U8 const sfTemplateEntryType = make::one(&sfTemplateEntryType, STI_UINT8, 2, "TemplateEntryType");
+SF_U8 const sfMethod = make::one(&sfMethod, STI_UINT8, 2, "Method");
SF_U8 const sfTransactionResult = make::one(&sfTransactionResult, STI_UINT8, 3, "TransactionResult");
// 16-bit integers
@@ -121,6 +121,8 @@ SF_U32 const sfReserveIncrement = make::one(&sfReserveIncrement
SF_U32 const sfSetFlag = make::one(&sfSetFlag, STI_UINT32, 33, "SetFlag");
SF_U32 const sfClearFlag = make::one(&sfClearFlag, STI_UINT32, 34, "ClearFlag");
SF_U32 const sfSignerQuorum = make::one(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum");
+SF_U32 const sfCancelAfter = make::one(&sfCancelAfter, STI_UINT32, 36, "CancelAfter");
+SF_U32 const sfFinishAfter = make::one(&sfFinishAfter, STI_UINT32, 37, "FinishAfter");
// 64-bit integers
SF_U64 const sfIndexNext = make::one(&sfIndexNext, STI_UINT64, 1, "IndexNext");
@@ -194,7 +196,7 @@ SF_Blob const sfMemoFormat = make::one(&sfMemoFormat, STI
// variable length (uncommon)
SF_Blob const sfMultiSignature = make::one(&sfMultiSignature, STI_VL, 16, "MultiSignature");
-SF_Blob const sfInnerSig = make::one(&sfInnerSig, STI_VL, 17, "InnerSig");
+SF_Blob const sfProof = make::one(&sfProof, STI_VL, 17, "Proof");
// account
SF_Account const sfAccount = make::one(&sfAccount, STI_ACCOUNT, 1, "Account");
diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp
index 8b5c5b11c..450885ffe 100644
--- a/src/ripple/protocol/impl/TxFormats.cpp
+++ b/src/ripple/protocol/impl/TxFormats.cpp
@@ -67,6 +67,25 @@ TxFormats::TxFormats ()
<< SOElement (sfDeliverMin, SOE_OPTIONAL)
;
+ add ("SuspendedPaymentCreate", ttSUSPAY_CREATE) <<
+ SOElement (sfDestination, SOE_REQUIRED) <<
+ SOElement (sfAmount, SOE_REQUIRED) <<
+ SOElement (sfDigest, SOE_OPTIONAL) <<
+ SOElement (sfCancelAfter, SOE_OPTIONAL) <<
+ SOElement (sfFinishAfter, SOE_OPTIONAL) <<
+ SOElement (sfDestinationTag, SOE_OPTIONAL);
+
+ add ("SuspendedPaymentFinish", ttSUSPAY_FINISH) <<
+ SOElement (sfOwner, SOE_REQUIRED) <<
+ SOElement (sfOfferSequence, SOE_REQUIRED) <<
+ SOElement (sfMethod, SOE_OPTIONAL) <<
+ SOElement (sfDigest, SOE_OPTIONAL) <<
+ SOElement (sfProof, SOE_OPTIONAL);
+
+ add ("SuspendedPaymentCancel", ttSUSPAY_CANCEL) <<
+ SOElement (sfOwner, SOE_REQUIRED) <<
+ SOElement (sfOfferSequence, SOE_REQUIRED);
+
add ("EnableAmendment", ttAMENDMENT)
<< SOElement (sfLedgerSequence, SOE_OPTIONAL)
<< SOElement (sfAmendment, SOE_REQUIRED)
diff --git a/src/ripple/rpc/handlers/Feature.cpp b/src/ripple/rpc/handlers/Feature1.cpp
similarity index 100%
rename from src/ripple/rpc/handlers/Feature.cpp
rename to src/ripple/rpc/handlers/Feature1.cpp
diff --git a/src/ripple/test/jtx.h b/src/ripple/test/jtx.h
index 8bf47e841..821716eaf 100644
--- a/src/ripple/test/jtx.h
+++ b/src/ripple/test/jtx.h
@@ -22,6 +22,7 @@
// Convenience header that includes everything
+#include
#include
#include
#include
@@ -46,6 +47,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/src/ripple/test/jtx/impl/tag.cpp b/src/ripple/test/jtx/impl/tag.cpp
new file mode 100644
index 000000000..ee51d0504
--- /dev/null
+++ b/src/ripple/test/jtx/impl/tag.cpp
@@ -0,0 +1,42 @@
+//------------------------------------------------------------------------------
+/*
+ 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
+dtag::operator()(Env const&, JTx& jt) const
+{
+ jt.jv["DestinationTag"] = value_;
+}
+
+void
+stag::operator()(Env const&, JTx& jt) const
+{
+ jt.jv["SourceTag"] = value_;
+}
+
+} // jtx
+} // test
+} // ripple
diff --git a/src/ripple/test/jtx/pay.h b/src/ripple/test/jtx/pay.h
index 60c513c3d..467c0a547 100644
--- a/src/ripple/test/jtx/pay.h
+++ b/src/ripple/test/jtx/pay.h
@@ -31,8 +31,7 @@ namespace jtx {
/** Create a payment. */
Json::Value
pay (Account const& account,
- Account const& to,
- AnyAmount amount);
+ Account const& to, AnyAmount amount);
} // jtx
} // test
diff --git a/src/ripple/test/jtx/tag.h b/src/ripple/test/jtx/tag.h
new file mode 100644
index 000000000..94e13f2c0
--- /dev/null
+++ b/src/ripple/test/jtx/tag.h
@@ -0,0 +1,69 @@
+//------------------------------------------------------------------------------
+/*
+ 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_TAG_H_INCLUDED
+#define RIPPLE_TEST_JTX_TAG_H_INCLUDED
+
+#include
+
+namespace ripple {
+namespace test {
+
+namespace jtx {
+
+/** Set the destination tag on a JTx*/
+struct dtag
+{
+private:
+ std::uint32_t value_;
+
+public:
+ explicit
+ dtag (std::uint32_t value)
+ : value_ (value)
+ {
+ }
+
+ void
+ operator()(Env const&, JTx& jt) const;
+};
+
+/** Set the source tag on a JTx*/
+struct stag
+{
+private:
+ std::uint32_t value_;
+
+public:
+ explicit
+ stag (std::uint32_t value)
+ : value_ (value)
+ {
+ }
+
+ void
+ operator()(Env const&, JTx& jt) const;
+};
+
+} // jtx
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/unity/app_tests.cpp b/src/ripple/unity/app_tests.cpp
index f27ccf031..dad4846a1 100644
--- a/src/ripple/unity/app_tests.cpp
+++ b/src/ripple/unity/app_tests.cpp
@@ -19,4 +19,5 @@
#include
+#include
#include
diff --git a/src/ripple/unity/app_tx.cpp b/src/ripple/unity/app_tx.cpp
index 02195db84..cb874838a 100644
--- a/src/ripple/unity/app_tx.cpp
+++ b/src/ripple/unity/app_tx.cpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp
index 79cf6b87d..580dd4b8b 100644
--- a/src/ripple/unity/rpcx.cpp
+++ b/src/ripple/unity/rpcx.cpp
@@ -46,7 +46,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
diff --git a/src/ripple/unity/test.cpp b/src/ripple/unity/test.cpp
index 932b9dcfa..d766d5475 100644
--- a/src/ripple/unity/test.cpp
+++ b/src/ripple/unity/test.cpp
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
#include
#include
#include