From 4da5bf541128bcfb85212d60d16ad4d8c965dd52 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 17 Aug 2025 20:47:19 +0200 Subject: [PATCH 1/3] feature-token-paychan --- include/xrpl/protocol/PayChan.h | 61 + include/xrpl/protocol/detail/features.macro | 1 + .../xrpl/protocol/detail/ledger_entries.macro | 2 + .../xrpl/protocol/detail/transactions.macro | 8 +- include/xrpl/protocol/jss.h | 1 + src/test/app/AMM_test.cpp | 2 +- src/test/app/EscrowToken_test.cpp | 57 - src/test/app/MPToken_test.cpp | 29 - src/test/app/PayChanToken_test.cpp | 3131 +++++++++++++++++ src/test/app/PayChan_test.cpp | 679 ++-- src/test/jtx.h | 1 + src/test/jtx/TestHelpers.h | 78 +- src/test/jtx/impl/TestHelpers.cpp | 145 +- src/test/jtx/impl/paychan.cpp | 166 + src/test/jtx/paychan.h | 119 + src/xrpld/app/main/Application.cpp | 4 +- src/xrpld/app/misc/EscrowUtils.h | 580 +++ src/xrpld/app/tx/detail/Escrow.cpp | 668 +--- src/xrpld/app/tx/detail/InvariantCheck.cpp | 11 +- src/xrpld/app/tx/detail/PayChan.cpp | 304 +- src/xrpld/rpc/handlers/GatewayBalances.cpp | 39 +- 21 files changed, 4881 insertions(+), 1205 deletions(-) create mode 100644 src/test/app/PayChanToken_test.cpp create mode 100644 src/test/jtx/impl/paychan.cpp create mode 100644 src/test/jtx/paychan.h create mode 100644 src/xrpld/app/misc/EscrowUtils.h diff --git a/include/xrpl/protocol/PayChan.h b/include/xrpl/protocol/PayChan.h index b552b591af2..20f8e8f0cde 100644 --- a/include/xrpl/protocol/PayChan.h +++ b/include/xrpl/protocol/PayChan.h @@ -38,6 +38,67 @@ serializePayChanAuthorization( msg.add64(amt.drops()); } +inline void +serializePayChanAuthorization( + Serializer& msg, + uint256 const& key, + IOUAmount const& amt, + Currency const& cur, + AccountID const& iss) +{ + msg.add32(HashPrefix::paymentChannelClaim); + msg.addBitString(key); + if (amt == beast::zero) + msg.add64(STAmount::cIssuedCurrency); + else if (amt.signum() == -1) // 512 = not native + msg.add64( + amt.mantissa() | + (static_cast(amt.exponent() + 512 + 97) + << (64 - 10))); + else // 256 = positive + msg.add64( + amt.mantissa() | + (static_cast(amt.exponent() + 512 + 256 + 97) + << (64 - 10))); + msg.addBitString(cur); + msg.addBitString(iss); +} + +inline void +serializePayChanAuthorization( + Serializer& msg, + uint256 const& key, + MPTAmount const& amt, + MPTID const& mptID, + AccountID const& iss) +{ + msg.add32(HashPrefix::paymentChannelClaim); + msg.addBitString(key); + msg.add64(amt.value()); + msg.addBitString(mptID); + msg.addBitString(iss); +} + +inline void +serializePayChanAuthorization( + Serializer& msg, + uint256 const& key, + STAmount const& amt) +{ + if (amt.native()) + serializePayChanAuthorization(msg, key, amt.xrp()); + else if (amt.holds()) + serializePayChanAuthorization( + msg, key, amt.iou(), amt.issue().currency, amt.issue().account); + else if (amt.holds()) + { + auto const mpt = amt.get(); + auto const mptID = mpt.getMptID(); + serializePayChanAuthorization( + msg, key, amt.mpt(), mptID, amt.getIssuer()); + } +} + } // namespace ripple #endif diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index e2725d1fc06..f28fa098b95 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(TokenPaychan, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 11306ee0f52..637ea63704c 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -377,6 +377,8 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({ {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED}, {sfDestinationNode, soeOPTIONAL}, + {sfTransferRate, soeOPTIONAL}, + {sfIssuerNode, soeOPTIONAL}, })) /** The ledger object which tracks the AMM. diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 89e9a16df5e..c3926c6a27e 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -122,7 +122,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, ({ /** This transaction type creates a new unidirectional XRP payment channel. */ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, ({ {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, {sfSettleDelay, soeREQUIRED}, {sfPublicKey, soeREQUIRED}, {sfCancelAfter, soeOPTIONAL}, @@ -132,15 +132,15 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, /** This transaction type funds an existing unidirectional XRP payment channel. */ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, ({ {sfChannel, soeREQUIRED}, - {sfAmount, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, {sfExpiration, soeOPTIONAL}, })) /** This transaction type submits a claim against an existing unidirectional payment channel. */ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ({ {sfChannel, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfBalance, soeOPTIONAL}, + {sfAmount, soeOPTIONAL, soeMPTSupported}, + {sfBalance, soeOPTIONAL, soeMPTSupported}, {sfSignature, soeOPTIONAL}, {sfPublicKey, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 67a045fa58d..66ffefcf7d0 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -262,6 +262,7 @@ JSS(errored); JSS(error_code); // out: error JSS(error_exception); // out: Submit JSS(error_message); // out: error +JSS(escrowed); // out: escrowed JSS(expand); // in: handler/Ledger JSS(expected_date); // out: any (warnings) JSS(expected_date_UTC); // out: any (warnings) diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index c89aebf813c..bb90edcb0ff 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -3665,7 +3665,7 @@ struct AMM_test : public jtx::AMMTest auto const settleDelay = 100s; NetClock::time_point const cancelAfter = env.current()->info().parentCloseTime + 200s; - env(create( + env(paychan::create( carol, ammAlice.ammAccount(), XRP(1'000), diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index e81064c825b..00edbb980b2 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -37,62 +37,6 @@ namespace test { struct EscrowToken_test : public beast::unit_test::suite { - static uint64_t - mptEscrowed( - jtx::Env const& env, - jtx::Account const& account, - jtx::MPT const& mpt) - { - auto const sle = env.le(keylet::mptoken(mpt.mpt(), account)); - if (sle && sle->isFieldPresent(sfLockedAmount)) - return (*sle)[sfLockedAmount]; - return 0; - } - - static uint64_t - issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt) - { - auto const sle = env.le(keylet::mptIssuance(mpt.mpt())); - if (sle && sle->isFieldPresent(sfLockedAmount)) - return (*sle)[sfLockedAmount]; - return 0; - } - - jtx::PrettyAmount - issuerBalance( - jtx::Env& env, - jtx::Account const& account, - Issue const& issue) - { - Json::Value params; - params[jss::account] = account.human(); - auto jrr = env.rpc("json", "gateway_balances", to_string(params)); - auto const result = jrr[jss::result]; - auto const obligations = - result[jss::obligations][to_string(issue.currency)]; - if (obligations.isNull()) - return {STAmount(issue, 0), account.name()}; - STAmount const amount = amountFromString(issue, obligations.asString()); - return {amount, account.name()}; - } - - jtx::PrettyAmount - issuerEscrowed( - jtx::Env& env, - jtx::Account const& account, - Issue const& issue) - { - Json::Value params; - params[jss::account] = account.human(); - auto jrr = env.rpc("json", "gateway_balances", to_string(params)); - auto const result = jrr[jss::result]; - auto const locked = result[jss::locked][to_string(issue.currency)]; - if (locked.isNull()) - return {STAmount(issue, 0), account.name()}; - STAmount const amount = amountFromString(issue, locked.asString()); - return {amount, account.name()}; - } - void testIOUEnablement(FeatureBitset features) { @@ -1566,7 +1510,6 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta); BEAST_EXPECT(env.balance(bob, USD) == USD(10'100)); } - // test rate change - lower { Env env{*this, features}; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 6470962f2f0..0d65c8ac4e9 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2151,35 +2151,6 @@ class MPToken_test : public beast::unit_test::suite jv = offer(alice, mpt, USD(100)); test(jv, jss::TakerGets.c_str()); } - // PaymentChannelCreate - { - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Account] = alice.human(); - jv[jss::Destination] = carol.human(); - jv[jss::SettleDelay] = 1; - jv[sfPublicKey.fieldName] = strHex(alice.pk().slice()); - jv[jss::Amount] = mpt.getJson(JsonOptions::none); - test(jv, jss::Amount.c_str()); - } - // PaymentChannelFund - { - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelFund; - jv[jss::Account] = alice.human(); - jv[sfChannel.fieldName] = to_string(uint256{1}); - jv[jss::Amount] = mpt.getJson(JsonOptions::none); - test(jv, jss::Amount.c_str()); - } - // PaymentChannelClaim - { - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelClaim; - jv[jss::Account] = alice.human(); - jv[sfChannel.fieldName] = to_string(uint256{1}); - jv[jss::Amount] = mpt.getJson(JsonOptions::none); - test(jv, jss::Amount.c_str()); - } // NFTokenCreateOffer { Json::Value jv; diff --git a/src/test/app/PayChanToken_test.cpp b/src/test/app/PayChanToken_test.cpp new file mode 100644 index 00000000000..018cd0c1cc8 --- /dev/null +++ b/src/test/app/PayChanToken_test.cpp @@ -0,0 +1,3131 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 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 + +namespace ripple { +namespace test { +struct PayChanToken_test : public beast::unit_test::suite +{ + void + testIOUEnablement(FeatureBitset features) + { + testcase("IOU Enablement"); + + using namespace jtx; + using namespace std::chrono; + + for (bool const withTokenPaychan : {false, true}) + { + auto const amend = + withTokenPaychan ? features : features - featureTokenPaychan; + Env env{*this, amend}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const openResult = + withTokenPaychan ? ter(tesSUCCESS) : ter(temBAD_AMOUNT); + auto const closeResult = + withTokenPaychan ? ter(tesSUCCESS) : ter(tecNO_TARGET); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), + openResult); + env.close(); + env(paychan::fund(alice, chan, USD(1'000)), openResult); + env.close(); + env(paychan::claim(bob, chan), txflags(tfClose), closeResult); + env.close(); + } + } + + void + testIOUAllowLockingFlag(FeatureBitset features) + { + testcase("IOU Allow Locking Flag"); + + using namespace jtx; + using namespace std::chrono; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + // Create PayChan + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // Clear the asfAllowTrustLineLocking flag + env(fclear(gw, asfAllowTrustLineLocking)); + env.close(); + env.require(nflags(gw, asfAllowTrustLineLocking)); + + // Cannot Create PayChan without asfAllowTrustLineLocking + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), + ter(tecNO_PERMISSION)); + env.close(); + + // Can Fund PayChan without asfAllowTrustLineLocking + env(paychan::fund(alice, chan, USD(1'000)), ter(tesSUCCESS)); + env.close(); + + // Can claim the paychan created before the flag was cleared + auto const sig = + paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), + ter(tesSUCCESS)); + env.close(); + } + + void + testIOUCreatePreflight(FeatureBitset features) + { + testcase("IOU Create Preflight"); + using namespace test::jtx; + using namespace std::literals; + + // temBAD_FEE: Exercises invalid preflight1. + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5'000), alice, bob, gw); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, USD(1), settleDelay, pk), + fee(XRP(-1)), + ter(temBAD_FEE)); + env.close(); + } + + // temBAD_AMOUNT: amount <= 0 + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5'000), alice, bob, gw); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, USD(-1), settleDelay, pk), + ter(temBAD_AMOUNT)); + env.close(); + } + + // temBAD_CURRENCY: badCurrency() == amount.getCurrency() + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const BAD = IOU(gw, badCurrency()); + env.fund(XRP(5'000), alice, bob, gw); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, BAD(1), settleDelay, pk), + ter(temBAD_CURRENCY)); + env.close(); + } + } + + void + testIOUCreatePreclaim(FeatureBitset features) + { + testcase("IOU Create Preclaim"); + using namespace test::jtx; + using namespace std::literals; + + // tecNO_PERMISSION: issuer is the same as the account + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + + env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), + ter(tecNO_PERMISSION)); + env.close(); + } + + // tecNO_ISSUER: Issuer does not exist + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob); + env.close(); + env.memoize(gw); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecNO_ISSUER)); + env.close(); + } + + // tecNO_PERMISSION: asfAllowTrustLineLocking is not set + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(5000))); + env(pay(gw, bob, USD(5000))); + env.close(); + + env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), + ter(tecNO_PERMISSION)); + env.close(); + } + + // tecNO_LINE: account does not have a trustline to the issuer + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecNO_LINE)); + env.close(); + } + + // tecNO_PERMISSION: Not testable + // tecNO_PERMISSION: Not testable + // tecNO_AUTH: requireAuth + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfRequireAuth)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecNO_AUTH)); + env.close(); + } + + // tecNO_AUTH: requireAuth + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + auto const aliceUSD = alice["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); + env.trust(USD(10'000), alice, bob); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecNO_AUTH)); + env.close(); + } + + // tecFROZEN: account is frozen + { + // Env Setup + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // set freeze on alice trustline + env(trust(gw, USD(10'000), alice, tfSetFreeze)); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecFROZEN)); + env.close(); + } + + // tecFROZEN: dest is frozen + { + // Env Setup + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze)); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecFROZEN)); + env.close(); + } + + // tecINSUFFICIENT_FUNDS + { + // Env Setup + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + } + + // tecINSUFFICIENT_FUNDS + { + // Env Setup + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + env(paychan::create(alice, bob, USD(10'001), 100s, alice.pk()), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + } + + // tecPRECISION_LOSS + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100000000000000000), alice); + env.trust(USD(100000000000000000), bob); + env.close(); + env(pay(gw, alice, USD(10000000000000000))); + env(pay(gw, bob, USD(1))); + env.close(); + + // alice cannot create paychan for 1/10 iou - precision loss + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tecPRECISION_LOSS)); + env.close(); + } + } + + void + testIOUClaimPreclaim(FeatureBitset features) + { + testcase("IOU Claim Preclaim"); + using namespace test::jtx; + using namespace std::literals; + + // tecNO_AUTH: requireAuth set: dest not authorized + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); + env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth)); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + env(pay(bob, gw, USD(10'000))); + env(trust(gw, bobUSD(0)), txflags(tfSetfAuth)); + env(trust(bob, USD(0))); + env.close(); + + env.trust(USD(10'000), bob); + env.close(); + + // bob cannot claim because he is not authorized + auto const sig = + paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim( + bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), + ter(tecNO_AUTH)); + env.close(); + } + + // tecFROZEN: issuer has deep frozen the dest + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze)); + + // bob cannot claim because of deep freeze + auto const sig = + paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim( + bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), + ter(tecFROZEN)); + env.close(); + } + } + + void + testIOUClaimDoApply(FeatureBitset features) + { + testcase("IOU Claim Do Apply"); + using namespace test::jtx; + using namespace std::literals; + + // tecNO_LINE_INSUF_RESERVE: insufficient reserve to create line + { + Env env{*this, features}; + auto const acctReserve = env.current()->fees().accountReserve(0); + auto const incReserve = env.current()->fees().increment; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, gw); + env.fund(acctReserve + (incReserve - 1), bob); + env.close(); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice); + env.close(); + env(pay(gw, alice, USD(10'000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + // bob cannot claim because insufficient reserve to create line + auto const sig = + paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim( + bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), + ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + } + + // tecNO_LINE: alice submits; claim IOU not created + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env.close(); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice); + env.close(); + env(pay(gw, alice, USD(10'000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + // alice cannot claim because bob does not have a trustline + env(paychan::claim(alice, chan, USD(1), USD(1)), ter(tecNO_LINE)); + env.close(); + } + + // tecLIMIT_EXCEEDED: alice submits; IOU Limit < balance + amount + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env.close(); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(1000), alice, bob); + env.close(); + env(pay(gw, alice, USD(1000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + env.trust(USD(1), bob); + env.close(); + + // alice cannot claim because bobs limit is too low + env(paychan::claim(alice, chan, USD(5), USD(5)), + ter(tecLIMIT_EXCEEDED)); + env.close(); + } + + // tesSUCCESS: bob submits; IOU Limit < balance + amount + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env.close(); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(1000), alice, bob); + env.close(); + env(pay(gw, alice, USD(1000))); + env.close(); + + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + env.trust(USD(1), bob); + env.close(); + + auto const bobPreLimit = env.limit(bob, USD); + + // bob can claim even if bobs limit is too low + auto const sig = + paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(5)); + env(paychan::claim( + bob, chan, USD(5), USD(5), Slice(sig), alice.pk()), + ter(tesSUCCESS)); + env.close(); + + // bobs limit is not changed + BEAST_EXPECT(env.limit(bob, USD) == bobPreLimit); + } + } + + void + testIOUBalances(FeatureBitset features) + { + testcase("IOU Balances"); + + using namespace jtx; + using namespace std::chrono; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const outstandingUSD = USD(10'000); + + // Create & Claim (Dest) PayChan + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + { + auto const preAliceUSD = env.balance(alice, USD); + auto const preBobUSD = env.balance(bob, USD); + env(paychan::create(alice, bob, USD(1'000), 1s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000)); + BEAST_EXPECT(env.balance(bob, USD) == preBobUSD); + BEAST_EXPECT( + issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000)); + } + { + auto const preAliceUSD = env.balance(alice, USD); + auto const preBobUSD = env.balance(bob, USD); + auto const sig = paychan::signClaimAuth( + alice.pk(), alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD); + BEAST_EXPECT(env.balance(bob, USD) == preBobUSD + USD(1'000)); + BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0)); + } + + // Create & Claim (Account) PayChan + auto const chan2 = paychan::channel(alice, bob, env.seq(alice)); + { + auto const preAliceUSD = env.balance(alice, USD); + auto const preBobUSD = env.balance(bob, USD); + env(paychan::create(alice, bob, USD(1'000), 100s, alice.pk()), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000)); + BEAST_EXPECT(env.balance(bob, USD) == preBobUSD); + BEAST_EXPECT( + issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000)); + } + { + auto const preAliceUSD = env.balance(alice, USD); + auto const preBobUSD = env.balance(bob, USD); + env(paychan::claim(alice, chan2, USD(1'000), USD(1'000)), + txflags(tfClose), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD); + BEAST_EXPECT(env.balance(bob, USD) == preBobUSD + USD(1'000)); + BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0)); + } + } + + void + testIOUMetaAndOwnership(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + { + testcase("IOU Metadata to other"); + + Env env{*this, features}; + env.fund(XRP(5000), alice, bob, carol, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob, carol); + env.close(); + env(pay(gw, alice, USD(5000))); + env(pay(gw, bob, USD(5000))); + env(pay(gw, carol, USD(5000))); + env.close(); + auto const aseq = env.seq(alice); + auto const bseq = env.seq(bob); + + auto const pk = alice.pk(); + auto const pk2 = bob.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + BEAST_EXPECT( + (*env.meta())[sfTransactionResult] == + static_cast(tesSUCCESS)); + env.close(); + env(paychan::create(bob, carol, USD(1'000), settleDelay, pk2)); + BEAST_EXPECT( + (*env.meta())[sfTransactionResult] == + static_cast(tesSUCCESS)); + env.close(); + + auto const ab = env.le(keylet::payChan(alice.id(), bob.id(), aseq)); + BEAST_EXPECT(ab); + + auto const bc = env.le(keylet::payChan(bob.id(), carol.id(), bseq)); + BEAST_EXPECT(bc); + + { + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) != aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) != bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); + BEAST_EXPECT( + std::find(cod.begin(), cod.end(), bc) != cod.end()); + + ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), ab) != iod.end()); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), bc) != iod.end()); + } + + auto const chan_ab = paychan::channel(alice, bob, aseq); + env(paychan::claim(alice, chan_ab, USD(1'000), USD(1'000)), + txflags(tfClose)); + { + BEAST_EXPECT( + !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT( + env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) == aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) != bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); + + ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), bc) != iod.end()); + } + + env.close(); + auto const chan_bc = paychan::channel(bob, carol, bseq); + env(paychan::claim(bob, chan_bc, USD(1'000), USD(1'000)), + txflags(tfClose)); + { + BEAST_EXPECT( + !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT( + !env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) == aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) == bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); + + ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), bc) == iod.end()); + } + } + + { + testcase("IOU Metadata to issuer"); + + Env env{*this, features}; + env.fund(XRP(5000), alice, carol, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, carol); + env.close(); + env(pay(gw, alice, USD(5000))); + env(pay(gw, carol, USD(5000))); + env.close(); + auto const aseq = env.seq(alice); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, gw, USD(1'000), settleDelay, pk)); + + BEAST_EXPECT( + (*env.meta())[sfTransactionResult] == + static_cast(tesSUCCESS)); + env.close(); + env(paychan::create(gw, carol, USD(1'000), settleDelay, alice.pk()), + ter(tecNO_PERMISSION)); + env.close(); + + auto const ag = env.le(keylet::payChan(alice.id(), gw.id(), aseq)); + BEAST_EXPECT(ag); + + { + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ag) != aod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); + + ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), ag) != iod.end()); + } + + auto const chan_ag = paychan::channel(alice, gw, aseq); + env(paychan::claim(alice, chan_ag, USD(1'000), USD(1'000)), + txflags(tfClose)); + { + BEAST_EXPECT( + !env.le(keylet::payChan(alice.id(), gw.id(), aseq))); + + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ag) == aod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); + + ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 2); + BEAST_EXPECT( + std::find(iod.begin(), iod.end(), ag) == iod.end()); + } + } + } + + void + testIOURippleState(FeatureBitset features) + { + testcase("IOU RippleState"); + using namespace test::jtx; + using namespace std::literals; + + struct TestAccountData + { + Account src; + Account dst; + Account gw; + bool hasTrustline; + bool negative; + }; + + std::array tests = {{ + // src > dst && src > issuer && dst no trustline + {Account("alice2"), Account("bob0"), Account{"gw0"}, false, true}, + // src < dst && src < issuer && dst no trustline + {Account("carol0"), Account("dan1"), Account{"gw1"}, false, false}, + // dst > src && dst > issuer && dst no trustline + {Account("dan1"), Account("alice2"), Account{"gw0"}, false, true}, + // dst < src && dst < issuer && dst no trustline + {Account("bob0"), Account("carol0"), Account{"gw1"}, false, false}, + // src > dst && src > issuer && dst has trustline + {Account("alice2"), Account("bob0"), Account{"gw0"}, true, true}, + // src < dst && src < issuer && dst has trustline + {Account("carol0"), Account("dan1"), Account{"gw1"}, true, false}, + // dst > src && dst > issuer && dst has trustline + {Account("dan1"), Account("alice2"), Account{"gw0"}, true, true}, + // dst < src && dst < issuer && dst has trustline + {Account("bob0"), Account("carol0"), Account{"gw1"}, true, false}, + }}; + + for (auto const& t : tests) + { + Env env{*this, features}; + auto const USD = t.gw["USD"]; + env.fund(XRP(5000), t.src, t.dst, t.gw); + env(fset(t.gw, asfAllowTrustLineLocking)); + env.close(); + + if (t.hasTrustline) + env.trust(USD(100'000), t.src, t.dst); + else + env.trust(USD(100'000), t.src); + env.close(); + + env(pay(t.gw, t.src, USD(10'000))); + if (t.hasTrustline) + env(pay(t.gw, t.dst, USD(10'000))); + env.close(); + + // src can create paychan + auto const seq1 = env.seq(t.src); + auto const delta = USD(1'000); + auto const pk = t.src.pk(); + auto const settleDelay = 100s; + env(paychan::create(t.src, t.dst, delta, settleDelay, pk)); + env.close(); + + // dst can claim paychan + auto const preSrc = env.balance(t.src, USD); + auto const preDst = env.balance(t.dst, USD); + + auto const chan = paychan::channel(t.src, t.dst, seq1); + auto const sig = + paychan::signClaimAuth(pk, t.src.sk(), chan, delta); + env(paychan::claim(t.dst, chan, delta, delta, Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(t.src, USD) == preSrc); + BEAST_EXPECT(env.balance(t.dst, USD) == preDst + delta); + } + } + + void + testIOUGateway(FeatureBitset features) + { + testcase("IOU Gateway"); + using namespace test::jtx; + using namespace std::literals; + + // issuer is source + { + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + Env env{*this, features}; + auto const USD = gw["USD"]; + env.fund(XRP(5000), alice, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.close(); + + env(pay(gw, alice, USD(10'000))); + env.close(); + + // issuer cannot create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + env(paychan::create(gw, alice, USD(1'000), settleDelay, pk), + ter(tecNO_PERMISSION)); + env.close(); + } + + struct TestAccountData + { + Account src; + Account dst; + bool hasTrustline; + }; + + std::array gwDstTests = {{ + // src > dst && src > issuer && dst has trustline + {Account("alice2"), Account{"gw0"}, true}, + // src < dst && src < issuer && dst has trustline + {Account("carol0"), Account{"gw1"}, true}, + // dst > src && dst > issuer && dst has trustline + {Account("dan1"), Account{"gw0"}, true}, + // dst < src && dst < issuer && dst has trustline + {Account("bob0"), Account{"gw1"}, true}, + }}; + + // issuer is destination + for (auto const& t : gwDstTests) + { + Env env{*this, features}; + auto const USD = t.dst["USD"]; + env.fund(XRP(5000), t.dst, t.src); + env(fset(t.dst, asfAllowTrustLineLocking)); + env.close(); + + env.trust(USD(100'000), t.src); + env.close(); + + env(pay(t.dst, t.src, USD(10'000))); + env.close(); + + // issuer can receive paychan + auto const seq1 = env.seq(t.src); + auto const preSrc = env.balance(t.src, USD); + auto const pk = t.src.pk(); + auto const settleDelay = 100s; + env(paychan::create(t.src, t.dst, USD(1'000), settleDelay, pk)); + env.close(); + + // issuer can claim paychan, no dest trustline + auto const chan = paychan::channel(t.src, t.dst, seq1); + auto const sig = + paychan::signClaimAuth(pk, t.src.sk(), chan, USD(1'000)); + env(paychan::claim( + t.dst, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + auto const preAmount = 10'000; + BEAST_EXPECT(preSrc == USD(preAmount)); + auto const postAmount = 9000; + BEAST_EXPECT(env.balance(t.src, USD) == USD(postAmount)); + BEAST_EXPECT(env.balance(t.dst, USD) == USD(0)); + } + } + + void + testIOULockedRate(FeatureBitset features) + { + testcase("IOU Locked Rate"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test locked rate + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, USD); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + auto const transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // bob can claim paychan + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD) == USD(10'100)); + } + // test rate change - higher + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, USD); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + auto transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // issuer changes rate higher + env(rate(gw, 1.26)); + env.close(); + + // bob can claim paychan - rate unchanged + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD) == USD(10'100)); + } + // test rate change - lower + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, USD); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + auto transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // bob can claim paychan - rate changed + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD) == USD(10125)); + } + + // test claim/close doesnt charge rate + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, USD); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + auto transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // alice can close paychan - rate is not charged + auto const chan = paychan::channel(alice, bob, seq1); + env(paychan::claim(bob, chan), txflags(tfClose)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAlice); + BEAST_EXPECT(env.balance(bob, USD) == USD(10000)); + } + } + + void + testIOULimitAmount(FeatureBitset features) + { + testcase("IOU Limit"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test LimitAmount + { + Env env{*this, features}; + env.fund(XRP(1'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(10'000), alice, bob); + env.close(); + env(pay(gw, alice, USD(1'000))); + env(pay(gw, bob, USD(1'000))); + env.close(); + + // alice can create paychan + auto seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // bob can claim + auto const preBobLimit = env.limit(bob, USD); + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + auto const postBobLimit = env.limit(bob, USD); + // bobs limit is NOT changed + BEAST_EXPECT(postBobLimit == preBobLimit); + } + } + + void + testIOURequireAuth(FeatureBitset features) + { + testcase("IOU Require Auth"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + + Env env{*this, features}; + env.fund(XRP(1'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); + env(trust(alice, USD(10'000))); + env(trust(bob, USD(10'000))); + env.close(); + env(pay(gw, alice, USD(1'000))); + env.close(); + + // alice cannot create paychan - fails without auth + auto seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + + // set auth on bob + env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth)); + env(trust(bob, USD(10'000))); + env.close(); + env(pay(gw, bob, USD(1'000))); + env.close(); + + // alice can create paychan - bob has auth + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // bob can claim + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + } + + void + testIOUFreeze(FeatureBitset features) + { + testcase("IOU Freeze"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test Global Freeze + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + env(fset(gw, asfGlobalFreeze)); + env.close(); + + // setup transaction + auto seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + + // create paychan fails - frozen trustline + env(paychan::create(alice, bob, delta, settleDelay, pk), + ter(tecFROZEN)); + env.close(); + + // clear global freeze + env(fclear(gw, asfGlobalFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set global freeze + env(fset(gw, asfGlobalFreeze)); + env.close(); + + // bob claim paychan success regardless of frozen assets + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + // clear global freeze + env(fclear(gw, asfGlobalFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set global freeze + env(fset(gw, asfGlobalFreeze)); + env.close(); + + // alice close paychan success regardless of frozen assets + auto const chan2 = paychan::channel(alice, bob, seq1); + env(paychan::claim(alice, chan2, delta, delta), txflags(tfClose)); + env.close(); + } + + // test Individual Freeze + { + // Env Setup + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // set freeze on alice trustline + env(trust(gw, USD(10'000), alice, tfSetFreeze)); + env.close(); + + // setup transaction + auto seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + + // create paychan fails - frozen trustline + env(paychan::create(alice, bob, delta, settleDelay, pk), + ter(tecFROZEN)); + env.close(); + + // clear freeze on alice trustline + env(trust(gw, USD(10'000), alice, tfClearFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze)); + env.close(); + + // bob claim paychan success regardless of frozen assets + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + // reset freeze on bob and alice trustline + env(trust(gw, USD(10'000), alice, tfClearFreeze)); + env(trust(gw, USD(10'000), bob, tfClearFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze)); + env.close(); + + // alice close paychan success regardless of frozen assets + auto const chan2 = paychan::channel(alice, bob, seq1); + env(paychan::claim(alice, chan2, delta, delta), txflags(tfClose)); + env.close(); + } + + // test Deep Freeze + { + // Env Setup + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(100'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // set freeze on alice trustline + env(trust(gw, USD(10'000), alice, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // setup transaction + auto seq1 = env.seq(alice); + auto const delta = USD(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + + // create paychan fails - frozen trustline + env(paychan::create(alice, bob, delta, settleDelay, pk), + ter(tecFROZEN)); + env.close(); + + // clear freeze on alice trustline + env(trust( + gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // bob claim paychan fails because of deep frozen assets + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk), + ter(tecFROZEN)); + env.close(); + + // reset freeze on alice and bob trustline + env(trust( + gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); + env(trust(gw, USD(10'000), bob, tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + // create paychan success + seq1 = env.seq(alice); + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + // set freeze on bob trustline + env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // bob close paychan success regardless of deep frozen assets + auto const chan2 = paychan::channel(alice, bob, seq1); + env(paychan::claim(bob, chan2), txflags(tfClose)); + env.close(); + } + } + + void + testIOUINSF(FeatureBitset features) + { + testcase("IOU Insufficient Funds"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + { + // test tecPATH_PARTIAL + // ie. has 10'000, paychan 1'000 then try to pay 10'000 + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + // create paychan success + auto const delta = USD(1'000); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + env(pay(alice, gw, USD(10'000)), ter(tecPATH_PARTIAL)); + } + { + // test tecINSUFFICIENT_FUNDS + // ie. has 10'000 paychan 1'000 then try to paychan 10'000 + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const delta = USD(1'000); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, delta, settleDelay, pk)); + env.close(); + + env(paychan::create(alice, bob, USD(10'000), settleDelay, pk), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + } + } + + void + testIOUPrecisionLoss(FeatureBitset features) + { + testcase("IOU Precision Loss"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // test min create precision loss + { + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100000000000000000), alice); + env.trust(USD(100000000000000000), bob); + env.close(); + env(pay(gw, alice, USD(10000000000000000))); + env(pay(gw, bob, USD(1))); + env.close(); + + // alice cannot create paychan for 1/10 iou - precision loss + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, USD(1), settleDelay, pk), + ter(tecPRECISION_LOSS)); + env.close(); + + auto const seq1 = env.seq(alice); + // alice can create paychan for 1'000 iou + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + // bob claim paychan success + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + } + } + + void + testMPTEnablement(FeatureBitset features) + { + testcase("MPT Enablement"); + + using namespace jtx; + using namespace std::chrono; + + for (bool const withTokenPaychan : {false, true}) + { + auto const amend = + withTokenPaychan ? features : features - featureTokenPaychan; + Env env{*this, amend}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(5000), bob); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const openResult = + withTokenPaychan ? ter(tesSUCCESS) : ter(temBAD_AMOUNT); + auto const closeResult = + withTokenPaychan ? ter(tesSUCCESS) : ter(tecNO_TARGET); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + openResult); + env.close(); + env(paychan::fund(alice, chan, MPT(1'000)), openResult); + env.close(); + env(paychan::claim(bob, chan), txflags(tfClose), closeResult); + env.close(); + } + } + + void + testMPTCreatePreflight(FeatureBitset features) + { + testcase("MPT Create Preflight"); + using namespace test::jtx; + using namespace std::literals; + + for (bool const withMPT : {true, false}) + { + auto const amend = + withMPT ? features : features - featureMPTokensV1; + Env env{*this, amend}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(1'000), alice, bob, gw); + + Json::Value jv = + paychan::create(alice, bob, XRP(1), 100s, alice.pk()); + jv.removeMember(jss::Amount); + jv[jss::Amount][jss::mpt_issuance_id] = + "00000004A407AF5856CCF3C42619DAA925813FC955C72983"; + jv[jss::Amount][jss::value] = "-1"; + + auto const result = withMPT ? ter(temBAD_AMOUNT) : ter(temDISABLED); + env(jv, result); + env.close(); + } + + // temBAD_AMOUNT: amount < 0 + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(-1), settleDelay, pk), + ter(temBAD_AMOUNT)); + env.close(); + } + } + + void + testMPTCreatePreclaim(FeatureBitset features) + { + testcase("MPT Create Preclaim"); + using namespace test::jtx; + using namespace std::literals; + + // tecNO_PERMISSION: issuer is the same as the account + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = gw.pk(); + auto const settleDelay = 100s; + env(paychan::create(gw, alice, MPT(1), settleDelay, pk), + ter(tecNO_PERMISSION)); + env.close(); + } + + // tecOBJECT_NOT_FOUND: mpt does not exist + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), alice, bob, gw); + env.close(); + + auto const mpt = ripple::test::jtx::MPT( + alice.name(), makeMptID(env.seq(alice), alice)); + Json::Value jv = + paychan::create(alice, bob, mpt(2), 100s, alice.pk()); + jv[jss::Amount][jss::mpt_issuance_id] = + "00000004A407AF5856CCF3C42619DAA925813FC955C72983"; + env(jv, ter(tecOBJECT_NOT_FOUND)); + env.close(); + } + + // tecNO_PERMISSION: tfMPTCanEscrow is not enabled + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(3), settleDelay, pk), + ter(tecNO_PERMISSION)); + env.close(); + } + + // tecOBJECT_NOT_FOUND: account does not have the mpt + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + auto const MPT = mptGw["MPT"]; + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(4), settleDelay, pk), + ter(tecOBJECT_NOT_FOUND)); + env.close(); + } + + // tecNO_AUTH: requireAuth set: account not authorized + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = + tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = gw, .holder = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + // unauthorize account + mptGw.authorize( + {.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(5), settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + } + + // tecNO_AUTH: requireAuth set: dest not authorized + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = + tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = gw, .holder = alice}); + mptGw.authorize({.account = bob}); + mptGw.authorize({.account = gw, .holder = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // unauthorize dest + mptGw.authorize( + {.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(6), settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + } + + // tecLOCKED: issuer has locked the account + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // lock account + mptGw.set({.account = gw, .holder = alice, .flags = tfMPTLock}); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(7), settleDelay, pk), + ter(tecLOCKED)); + env.close(); + } + + // tecLOCKED: issuer has locked the dest + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // lock dest + mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock}); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(8), settleDelay, pk), + ter(tecLOCKED)); + env.close(); + } + + // tecNO_AUTH: mpt cannot be transferred + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(9), settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + } + + // tecINSUFFICIENT_FUNDS: spendable amount is zero + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, bob, MPT(10))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(11), settleDelay, pk), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + } + + // tecINSUFFICIENT_FUNDS: spendable amount is less than the amount + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10))); + env(pay(gw, bob, MPT(10))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(11), settleDelay, pk), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + } + } + + void + testMPTClaimPreclaim(FeatureBitset features) + { + testcase("MPT Claim Preclaim"); + using namespace test::jtx; + using namespace std::literals; + + // tecNO_AUTH: requireAuth set: dest not authorized + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = + tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = gw, .holder = alice}); + mptGw.authorize({.account = bob}); + mptGw.authorize({.account = gw, .holder = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // unauthorize dest + mptGw.authorize( + {.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), + ter(tecNO_AUTH)); + env.close(); + } + + // tecLOCKED: issuer has locked the dest + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(8), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // lock dest + mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock}); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(8)); + env(paychan::claim(bob, chan, MPT(8), MPT(8), Slice(sig), pk), + ter(tecLOCKED)); + env.close(); + } + } + + void + testMPTClaimDoApply(FeatureBitset features) + { + testcase("MPT Claim Do Apply"); + using namespace test::jtx; + using namespace std::literals; + + // tecINSUFFICIENT_RESERVE: insufficient reserve to create MPT + { + Env env{*this, features}; + auto const acctReserve = env.current()->fees().accountReserve(0); + auto const incReserve = env.current()->fees().increment; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(acctReserve + (incReserve - 1), bob); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + + // tesSUCCESS: bob submits; claim MPT created + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), bob); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), + ter(tesSUCCESS)); + env.close(); + } + + // tecNO_PERMISSION: alice submits; claim MPT not created + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), bob); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + env(paychan::claim(alice, chan, MPT(10), MPT(10)), + ter(tecNO_PERMISSION)); + env.close(); + } + } + + void + testMPTBalances(FeatureBitset features) + { + testcase("MPT Balances"); + + using namespace jtx; + using namespace std::chrono; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + env.fund(XRP(5000), bob); + + MPTTester mptGw(env, gw, {.holders = {alice, carol}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = carol}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, carol, MPT(10'000))); + env.close(); + + auto outstandingMPT = env.balance(gw, MPT); + + // Create & Claim (Dest) PayChan + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + { + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000); + } + { + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const pk = alice.pk(); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim( + bob, chan, MPT(1'000), MPT(1'000), Slice(sig), pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + } + + // Create & Claim (Account) PayChan + auto const chan2 = paychan::channel(alice, bob, env.seq(alice)); + { + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000); + } + { + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + env(paychan::claim(alice, chan2, MPT(1'000), MPT(1'000)), + txflags(tfClose), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + } + + // Multiple PayChans + { + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const preCarolMPT = env.balance(carol, MPT); + auto const pk = alice.pk(); + auto const pk2 = carol.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + env(paychan::create(carol, bob, MPT(1'000), settleDelay, pk2), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(carol, MPT) == preCarolMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, carol, MPT) == 1'000); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 2'000); + } + + // Max MPT Amount Issued (PayChan 1 MPT) + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(maxMPTokenAmount))); + env.close(); + + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const outstandingMPT = env.balance(gw, MPT); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(1), settleDelay, pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1)); + env(paychan::claim(bob, chan, MPT(1), MPT(1), Slice(sig), pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), alice)) + ->isFieldPresent(sfLockedAmount)); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1)); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + BEAST_EXPECT(!env.le(keylet::mptIssuance(MPT.mpt())) + ->isFieldPresent(sfLockedAmount)); + } + + // Max MPT Amount Issued (PayChan Max MPT) + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(maxMPTokenAmount))); + env.close(); + + auto const preAliceMPT = env.balance(alice, MPT); + auto const preBobMPT = env.balance(bob, MPT); + auto const outstandingMPT = env.balance(gw, MPT); + + // PayChan Max MPT - 10 + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan1 = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create( + alice, bob, MPT(maxMPTokenAmount - 10), settleDelay, pk)); + env.close(); + + // PayChan 10 MPT + auto const chan2 = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk)); + env.close(); + + BEAST_EXPECT( + env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == maxMPTokenAmount); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == maxMPTokenAmount); + + auto const sig1 = paychan::signClaimAuth( + pk, alice.sk(), chan1, MPT(maxMPTokenAmount - 10)); + env(paychan::claim( + bob, + chan1, + MPT(maxMPTokenAmount - 10), + MPT(maxMPTokenAmount - 10), + Slice(sig1), + pk), + ter(tesSUCCESS)); + env.close(); + + auto const sig2 = + paychan::signClaimAuth(pk, alice.sk(), chan2, MPT(10)); + env(paychan::claim(bob, chan2, MPT(10), MPT(10), Slice(sig2), pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT( + env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT( + env.balance(bob, MPT) == preBobMPT + MPT(maxMPTokenAmount)); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + } + } + + void + testMPTMetaAndOwnership(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + { + testcase("MPT Metadata to other"); + + Env env{*this, features}; + MPTTester mptGw(env, gw, {.holders = {alice, bob, carol}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + mptGw.authorize({.account = carol}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env(pay(gw, carol, MPT(10'000))); + env.close(); + auto const aseq = env.seq(alice); + auto const bseq = env.seq(bob); + + auto const pk = alice.pk(); + auto const pk2 = bob.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk)); + BEAST_EXPECT( + (*env.meta())[sfTransactionResult] == + static_cast(tesSUCCESS)); + env.close(); + env(paychan::create(bob, carol, MPT(1'000), settleDelay, pk2)); + BEAST_EXPECT( + (*env.meta())[sfTransactionResult] == + static_cast(tesSUCCESS)); + env.close(); + + auto const ab = env.le(keylet::payChan(alice.id(), bob.id(), aseq)); + BEAST_EXPECT(ab); + + auto const bc = env.le(keylet::payChan(bob.id(), carol.id(), bseq)); + BEAST_EXPECT(bc); + + { + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) != aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) != bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); + BEAST_EXPECT( + std::find(cod.begin(), cod.end(), bc) != cod.end()); + } + + auto const chan_ab = paychan::channel(alice, bob, aseq); + env(paychan::claim(alice, chan_ab, MPT(1'000), MPT(1'000)), + txflags(tfClose)); + { + BEAST_EXPECT( + !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT( + env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) == aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) != bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); + } + + env.close(); + auto const chan_bc = paychan::channel(bob, carol, bseq); + env(paychan::claim(bob, chan_bc, MPT(1'000), MPT(1'000)), + txflags(tfClose)); + { + BEAST_EXPECT( + !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT( + !env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + + ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); + BEAST_EXPECT( + std::find(aod.begin(), aod.end(), ab) == aod.end()); + + ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + std::find(bod.begin(), bod.end(), bc) == bod.end()); + + ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); + } + } + } + + void + testMPTGateway(FeatureBitset features) + { + testcase("MPT Gateway Balances"); + using namespace test::jtx; + using namespace std::literals; + + // issuer is source + { + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + Env env{*this, features}; + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + // issuer cannot create paychan + auto const pk = gw.pk(); + auto const settleDelay = 100s; + env(paychan::create(gw, alice, MPT(1'000), settleDelay, pk), + ter(tecNO_PERMISSION)); + env.close(); + } + + // issuer is dest; alice w/ authorization + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + // issuer can be destination + auto const preAliceMPT = env.balance(alice, MPT); + auto const preOutstanding = env.balance(gw, MPT); + auto const preEscrowed = issuerMPTEscrowed(env, MPT); + BEAST_EXPECT(preOutstanding == MPT(10'000)); + BEAST_EXPECT(preEscrowed == 0); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, gw, env.seq(alice)); + env(paychan::create(alice, gw, MPT(1'000), settleDelay, pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000); + BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed + 1'000); + + // issuer (dest) can claim paychan + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim( + gw, chan, MPT(1'000), MPT(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding - MPT(1'000)); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed); + } + } + + void + testMPTLockedRate(FeatureBitset features) + { + testcase("MPT Locked Rate"); + using namespace test::jtx; + using namespace std::literals; + + // test locked rate: claim + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.transferFee = 25000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, MPT); + auto const seq1 = env.seq(alice); + auto const delta = MPT(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(125), settleDelay, pk)); + env.close(); + auto const transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // bob can claim paychan + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, MPT) == MPT(10'100)); + } + + // test locked rate: close + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.transferFee = 25000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // alice can create paychan w/ xfer rate + auto const preAlice = env.balance(alice, MPT); + auto const preBob = env.balance(bob, MPT); + auto const seq1 = env.seq(alice); + auto const delta = MPT(125); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(125), settleDelay, pk)); + env.close(); + auto const transferRate = paychan::rate(env, alice, bob, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // bob can close paychan + auto const chan = paychan::channel(alice, bob, seq1); + env(paychan::claim(bob, chan), txflags(tfClose)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAlice); + BEAST_EXPECT(env.balance(bob, MPT) == preBob); + } + } + + // void + // testMPTRequireAuth(FeatureBitset features) + // { + // testcase("MPT Require Auth"); + // using namespace test::jtx; + // using namespace std::literals; + + // Env env{*this, features}; + // auto const baseFee = env.current()->fees().base; + // auto const alice = Account("alice"); + // auto const bob = Account("bob"); + // auto const gw = Account("gw"); + + // MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + // mptGw.create( + // {.ownerCount = 1, + // .holderCount = 0, + // .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + // mptGw.authorize({.account = alice}); + // mptGw.authorize({.account = gw, .holder = alice}); + // mptGw.authorize({.account = bob}); + // mptGw.authorize({.account = gw, .holder = bob}); + // auto const MPT = mptGw["MPT"]; + // env(pay(gw, alice, MPT(10'000))); + // env.close(); + + // auto seq = env.seq(alice); + // auto const delta = MPT(125); + // // alice can create escrow - is authorized + // env(escrow::create(alice, bob, MPT(100)), + // escrow::condition(escrow::cb1), + // escrow::finish_time(env.now() + 1s), + // fee(baseFee * 150)); + // env.close(); + + // // bob can finish escrow - is authorized + // env(escrow::finish(bob, alice, seq), + // escrow::condition(escrow::cb1), + // escrow::fulfillment(escrow::fb1), + // fee(baseFee * 150)); + // env.close(); + // } + + void + testMPTLock(FeatureBitset features) + { + testcase("MPT Lock"); + using namespace test::jtx; + using namespace std::literals; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // alice create paychan + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(100), settleDelay, pk)); + env.close(); + + // lock account & dest + mptGw.set({.account = gw, .holder = alice, .flags = tfMPTLock}); + mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock}); + + // bob cannot claim + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(100)); + env(paychan::claim(bob, chan, MPT(100), MPT(100), Slice(sig), pk), + ter(tecLOCKED)); + env.close(); + + // bob can claim/close + env(paychan::claim(bob, chan), txflags(tfClose)); + env.close(); + } + + void + testMPTCanTransfer(FeatureBitset features) + { + testcase("MPT Can Transfer"); + using namespace test::jtx; + using namespace std::literals; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // alice cannot create paychan to non issuer + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(100), settleDelay, pk), + ter(tecNO_AUTH)); + env.close(); + + // PayChan Create & Claim + { + // alice can create paychan to issuer + auto const chan = paychan::channel(alice, gw, env.seq(alice)); + env(paychan::create(alice, gw, MPT(100), settleDelay, pk)); + env.close(); + + // gw can claim + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(100)); + env(paychan::claim(gw, chan, MPT(100), MPT(100), Slice(sig), pk)); + env.close(); + } + + // PayChan Create & Close + { + // alice can create paychan to issuer + auto const chan = paychan::channel(alice, gw, env.seq(alice)); + env(paychan::create(alice, gw, MPT(100), settleDelay, pk)); + env.close(); + + // gw can claim/close + env(paychan::claim(gw, chan), txflags(tfClose)); + env.close(); + } + } + + void + testMPTDestroy(FeatureBitset features) + { + testcase("MPT Destroy"); + using namespace test::jtx; + using namespace std::literals; + + // tecHAS_OBLIGATIONS: issuer cannot destroy issuance + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk)); + env.close(); + + env(pay(alice, gw, MPT(10'000)), ter(tecPATH_PARTIAL)); + env(pay(alice, gw, MPT(9'990))); + env(pay(bob, gw, MPT(10'000))); + BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10); + BEAST_EXPECT(env.balance(bob, MPT) == MPT(0)); + BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(10)); + mptGw.authorize({.account = bob, .flags = tfMPTUnauthorize}); + mptGw.destroy( + {.id = mptGw.issuanceID(), + .ownerCount = 1, + .err = tecHAS_OBLIGATIONS}); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), + ter(tesSUCCESS)); + env.close(); + + env(pay(bob, gw, MPT(10))); + mptGw.destroy({.id = mptGw.issuanceID(), .ownerCount = 0}); + } + + // tecHAS_OBLIGATIONS: holder cannot destroy mptoken + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), bob); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + env(pay(alice, gw, MPT(9'990))); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10); + mptGw.authorize( + {.account = alice, + .flags = tfMPTUnauthorize, + .err = tecHAS_OBLIGATIONS}); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + mptGw.authorize({.account = alice, .flags = tfMPTUnauthorize}); + BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), alice))); + } + } + + void + testIOUWithFeats(FeatureBitset features) + { + testIOUEnablement(features); + testIOUAllowLockingFlag(features); + testIOUCreatePreflight(features); + testIOUCreatePreclaim(features); + testIOUClaimPreclaim(features); + testIOUClaimDoApply(features); + // testIOUClaimClosePreclaim(features); + testIOUBalances(features); + testIOUMetaAndOwnership(features); + testIOURippleState(features); + testIOUGateway(features); + testIOULockedRate(features); + testIOULimitAmount(features); + testIOURequireAuth(features); + testIOUFreeze(features); + testIOUINSF(features); + testIOUPrecisionLoss(features); + } + + void + testMPTWithFeats(FeatureBitset features) + { + testMPTEnablement(features); + testMPTCreatePreflight(features); + testMPTCreatePreclaim(features); + testMPTClaimPreclaim(features); + testMPTClaimDoApply(features); + // testMPTClaimClosePreclaim(features); + testMPTBalances(features); + testMPTMetaAndOwnership(features); + testMPTGateway(features); + testMPTLockedRate(features); + // testMPTRequireAuth(features); + testMPTLock(features); + testMPTCanTransfer(features); + testMPTDestroy(features); + } + +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{testable_amendments()}; + testIOUWithFeats(all); + testMPTWithFeats(all); + } +}; + +BEAST_DEFINE_TESTSUITE(PayChanToken, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 3a5d3d6ff5e..7dcb5471007 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -60,15 +60,6 @@ struct PayChan_test : public beast::unit_test::suite return sign(pk, sk, msg.slice()); } - static STAmount - channelAmount(ReadView const& view, uint256 const& chan) - { - auto const slep = view.read({ltPAYCHAN, chan}); - if (!slep) - return XRPAmount{-1}; - return (*slep)[sfAmount]; - } - static std::optional channelExpiration(ReadView const& view, uint256 const& chan) { @@ -93,68 +84,72 @@ struct PayChan_test : public beast::unit_test::suite env.fund(XRP(10000), alice, bob); auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000)); { auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops); } - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); BEAST_EXPECT(chanBal == XRP(0)); BEAST_EXPECT(chanAmt == XRP(2000)); { // bad amounts (non-xrp, negative amounts) - env(create(alice, bob, USDA(1000), settleDelay, pk), - ter(temBAD_AMOUNT)); - env(fund(alice, chan, USDA(1000)), ter(temBAD_AMOUNT)); - env(create(alice, bob, XRP(-1000), settleDelay, pk), + // env(paychan::create(alice, bob, USDA(1000), settleDelay, pk), + // ter(temBAD_AMOUNT)); + // env(paychan::fund(alice, chan, USDA(1000)), ter(temBAD_AMOUNT)); + env(paychan::create(alice, bob, XRP(-1000), settleDelay, pk), ter(temBAD_AMOUNT)); - env(fund(alice, chan, XRP(-1000)), ter(temBAD_AMOUNT)); + env(paychan::fund(alice, chan, XRP(-1000)), ter(temBAD_AMOUNT)); } // invalid account - env(create(alice, "noAccount", XRP(1000), settleDelay, pk), + env(paychan::create(alice, "noAccount", XRP(1000), settleDelay, pk), ter(tecNO_DST)); // can't create channel to the same account - env(create(alice, alice, XRP(1000), settleDelay, pk), + env(paychan::create(alice, alice, XRP(1000), settleDelay, pk), ter(temDST_IS_SRC)); // invalid channel - env(fund( + env(paychan::fund( alice, - channel(alice, "noAccount", env.seq(alice) - 1), + paychan::channel(alice, "noAccount", env.seq(alice) - 1), XRP(1000)), ter(tecNO_ENTRY)); // not enough funds - env(create(alice, bob, XRP(10000), settleDelay, pk), ter(tecUNFUNDED)); - - { - // No signature claim with bad amounts (negative and non-xrp) - auto const iou = USDA(100).value(); - auto const negXRP = XRP(-100).value(); - auto const posXRP = XRP(100).value(); - env(claim(alice, chan, iou, iou), ter(temBAD_AMOUNT)); - env(claim(alice, chan, posXRP, iou), ter(temBAD_AMOUNT)); - env(claim(alice, chan, iou, posXRP), ter(temBAD_AMOUNT)); - env(claim(alice, chan, negXRP, negXRP), ter(temBAD_AMOUNT)); - env(claim(alice, chan, posXRP, negXRP), ter(temBAD_AMOUNT)); - env(claim(alice, chan, negXRP, posXRP), ter(temBAD_AMOUNT)); - } + env(paychan::create(alice, bob, XRP(10000), settleDelay, pk), + ter(tecUNFUNDED)); + + // { + // // No signature claim with bad amounts (negative and non-xrp) + // auto const iou = USDA(100).value(); + // auto const negXRP = XRP(-100).value(); + // auto const posXRP = XRP(100).value(); + // env(paychan::claim(alice, chan, iou, iou), ter(temBAD_AMOUNT)); + // env(paychan::claim(alice, chan, posXRP, iou), + // ter(temBAD_AMOUNT)); env(paychan::claim(alice, chan, iou, + // posXRP), ter(temBAD_AMOUNT)); env(paychan::claim(alice, chan, + // negXRP, negXRP), ter(temBAD_AMOUNT)); env(paychan::claim(alice, + // chan, posXRP, negXRP), ter(temBAD_AMOUNT)); + // env(paychan::claim(alice, chan, negXRP, posXRP), + // ter(temBAD_AMOUNT)); + // } { // No signature claim more than authorized auto const delta = XRP(500); auto const reqBal = chanBal + delta; auto const authAmt = reqBal + XRP(-100); assert(reqBal <= chanAmt); - env(claim(alice, chan, reqBal, authAmt), ter(temBAD_AMOUNT)); + env(paychan::claim(alice, chan, reqBal, authAmt), + ter(temBAD_AMOUNT)); } { // No signature needed since the owner is claiming @@ -163,9 +158,11 @@ struct PayChan_test : public beast::unit_test::suite auto const reqBal = chanBal + delta; auto const authAmt = reqBal + XRP(100); assert(reqBal <= chanAmt); - env(claim(alice, chan, reqBal, authAmt)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::claim(alice, chan, reqBal, authAmt)); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } @@ -178,19 +175,25 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; // claim again preBob = env.balance(bob); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ter(tecUNFUNDED_PAYMENT)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); } { @@ -201,22 +204,25 @@ struct PayChan_test : public beast::unit_test::suite assert(reqAmt <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()), + env(paychan::claim( + bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()), ter(temBAD_AMOUNT)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob); } // Dst tries to fund the channel - env(fund(bob, chan, XRP(1000)), ter(tecNO_PERMISSION)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::fund(bob, chan, XRP(1000)), ter(tecNO_PERMISSION)); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); { // Wrong signing key auto const sig = signClaimAuth(bob.pk(), bob.sk(), chan, XRP(1500)); - env(claim( + env(paychan::claim( bob, chan, XRP(1500).value(), @@ -224,13 +230,15 @@ struct PayChan_test : public beast::unit_test::suite Slice(sig), bob.pk()), ter(temBAD_SIGNER)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); } { // Bad signature auto const sig = signClaimAuth(bob.pk(), bob.sk(), chan, XRP(1500)); - env(claim( + env(paychan::claim( bob, chan, XRP(1500).value(), @@ -238,15 +246,17 @@ struct PayChan_test : public beast::unit_test::suite Slice(sig), alice.pk()), ter(temBAD_SIGNATURE)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); } { // Dst closes channel auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - env(claim(bob, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(bob, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); auto const feeDrops = env.current()->fees().base; auto const delta = chanAmt - chanBal; assert(delta > beast::zero); @@ -288,10 +298,10 @@ struct PayChan_test : public beast::unit_test::suite // channel creation from alice to bob is disallowed { - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk), + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } // set flag on alice also @@ -300,10 +310,10 @@ struct PayChan_test : public beast::unit_test::suite // channel creation from bob to alice is now disallowed { - auto const chan = channel(bob, alice, env.seq(bob)); - env(create(bob, alice, XRP(1000), settleDelay, pk), + auto const chan = paychan::channel(bob, alice, env.seq(bob)); + env(paychan::create(bob, alice, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } // remove flag from bob @@ -312,18 +322,18 @@ struct PayChan_test : public beast::unit_test::suite // now the channel between alice and bob can exist { - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk), + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk), ter(tesSUCCESS)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); } // a channel from cho to alice isn't allowed { - auto const chan = channel(cho, alice, env.seq(cho)); - env(create(cho, alice, XRP(1000), settleDelay, pk), + auto const chan = paychan::channel(cho, alice, env.seq(cho)); + env(paychan::create(cho, alice, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } // remove flag from alice @@ -332,10 +342,10 @@ struct PayChan_test : public beast::unit_test::suite // now a channel from cho to alice is allowed { - auto const chan = channel(cho, alice, env.seq(cho)); - env(create(cho, alice, XRP(1000), settleDelay, pk), + auto const chan = paychan::channel(cho, alice, env.seq(cho)); + env(paychan::create(cho, alice, XRP(1000), settleDelay, pk), ter(tesSUCCESS)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); } } @@ -357,14 +367,17 @@ struct PayChan_test : public beast::unit_test::suite NetClock::time_point const cancelAfter = env.current()->info().parentCloseTime + 3600s; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create( + alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env.close(cancelAfter); { // dst cannot claim after cancelAfter - auto const chanBal = channelBalance(*env.current(), chan); - auto const chanAmt = channelAmount(*env.current(), chan); + auto const chanBal = + paychan::channelBalance(*env.current(), chan); + auto const chanAmt = + paychan::channelAmount(*env.current(), chan); auto preAlice = env.balance(alice); auto preBob = env.balance(bob); auto const delta = XRP(500); @@ -373,9 +386,10 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); auto const feeDrops = env.current()->fees().base; - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); BEAST_EXPECT(env.balance(alice) == preAlice + channelFunds); } @@ -389,17 +403,20 @@ struct PayChan_test : public beast::unit_test::suite NetClock::time_point const cancelAfter = env.current()->info().parentCloseTime + 3600s; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create( + alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); // third party close before cancelAfter - env(claim(carol, chan), txflags(tfClose), ter(tecNO_PERMISSION)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + env(paychan::claim(carol, chan), + txflags(tfClose), + ter(tecNO_PERMISSION)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env.close(cancelAfter); // third party close after cancelAfter auto const preAlice = env.balance(alice); - env(claim(carol, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(carol, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(env.balance(alice) == preAlice + channelFunds); } // fixPayChanCancelAfter @@ -421,7 +438,7 @@ struct PayChan_test : public beast::unit_test::suite env.current()->info().parentCloseTime - 1s; auto const txResult = withFixPayChan ? ter(tecEXPIRED) : ter(tesSUCCESS); - env(create( + env(paychan::create( alice, bob, channelFunds, settleDelay, pk, cancelAfter), txResult); } @@ -443,7 +460,7 @@ struct PayChan_test : public beast::unit_test::suite auto const channelFunds = XRP(1000); NetClock::time_point const cancelAfter = env.current()->info().parentCloseTime; - env(create( + env(paychan::create( alice, bob, channelFunds, settleDelay, pk, cancelAfter), ter(tesSUCCESS)); } @@ -467,53 +484,55 @@ struct PayChan_test : public beast::unit_test::suite auto const minExpiration = closeTime + settleDelay; NetClock::time_point const cancelAfter = closeTime + 7200s; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create( + alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); // Owner closes, will close after settleDelay - env(claim(alice, chan), txflags(tfClose)); + env(paychan::claim(alice, chan), txflags(tfClose)); auto counts = [](auto const& t) { return t.time_since_epoch().count(); }; BEAST_EXPECT( *channelExpiration(*env.current(), chan) == counts(minExpiration)); // increase the expiration time - env(fund( + env(paychan::fund( alice, chan, XRP(1), NetClock::time_point{minExpiration + 100s})); BEAST_EXPECT( *channelExpiration(*env.current(), chan) == counts(minExpiration) + 100); // decrease the expiration, but still above minExpiration - env(fund( + env(paychan::fund( alice, chan, XRP(1), NetClock::time_point{minExpiration + 50s})); BEAST_EXPECT( *channelExpiration(*env.current(), chan) == counts(minExpiration) + 50); // decrease the expiration below minExpiration - env(fund( + env(paychan::fund( alice, chan, XRP(1), NetClock::time_point{minExpiration - 50s}), ter(temBAD_EXPIRATION)); BEAST_EXPECT( *channelExpiration(*env.current(), chan) == counts(minExpiration) + 50); - env(claim(bob, chan), txflags(tfRenew), ter(tecNO_PERMISSION)); + env(paychan::claim(bob, chan), txflags(tfRenew), ter(tecNO_PERMISSION)); BEAST_EXPECT( *channelExpiration(*env.current(), chan) == counts(minExpiration) + 50); - env(claim(alice, chan), txflags(tfRenew)); + env(paychan::claim(alice, chan), txflags(tfRenew)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); // decrease the expiration below minExpiration - env(fund( + env(paychan::fund( alice, chan, XRP(1), NetClock::time_point{minExpiration - 50s}), ter(temBAD_EXPIRATION)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); - env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration})); + env(paychan::fund( + alice, chan, XRP(1), NetClock::time_point{minExpiration})); env.close(minExpiration); // Try to extend the expiration after the expiration has already passed - env(fund( + env(paychan::fund( alice, chan, XRP(1), NetClock::time_point{minExpiration + 1000s})); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } void @@ -531,17 +550,17 @@ struct PayChan_test : public beast::unit_test::suite NetClock::time_point const settleTimepoint = env.current()->info().parentCloseTime + settleDelay; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); // Owner closes, will close after settleDelay - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env.close(settleTimepoint - settleDelay / 2); { // receiver can still claim - auto const chanBal = channelBalance(*env.current(), chan); - auto const chanAmt = channelAmount(*env.current(), chan); + auto const chanBal = paychan::channelBalance(*env.current(), chan); + auto const chanAmt = paychan::channelAmount(*env.current(), chan); auto preBob = env.balance(bob); auto const delta = XRP(500); auto const reqBal = chanBal + delta; @@ -549,17 +568,20 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); } env.close(settleTimepoint); { // past settleTime, channel will close - auto const chanBal = channelBalance(*env.current(), chan); - auto const chanAmt = channelAmount(*env.current(), chan); + auto const chanBal = paychan::channelBalance(*env.current(), chan); + auto const chanAmt = paychan::channelAmount(*env.current(), chan); auto const preAlice = env.balance(alice); auto preBob = env.balance(bob); auto const delta = XRP(500); @@ -568,8 +590,9 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(alice) == preAlice + chanAmt - chanBal); BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); @@ -589,23 +612,25 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); // Owner tries to close channel, but it will remain open (settle delay) - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); { // claim the entire amount auto const preBob = env.balance(bob); - env(claim(alice, chan, channelFunds.value(), channelFunds.value())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == channelFunds); + env(paychan::claim( + alice, chan, channelFunds.value(), channelFunds.value())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == channelFunds); BEAST_EXPECT(env.balance(bob) == preBob + channelFunds); } auto const preAlice = env.balance(alice); // Channel is now dry, can close before expiration date - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); } @@ -624,15 +649,15 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); // Owner tries to close channel, but it will remain open (settle delay) - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); { - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); auto const preBob = env.balance(bob); auto const delta = XRP(500); @@ -640,16 +665,18 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, reqBal); - env(claim(bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); + env(paychan::claim( + bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; } { // Claim again - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); auto const preBob = env.balance(bob); auto const delta = XRP(500); @@ -657,8 +684,10 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, reqBal); - env(claim(bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); + env(paychan::claim( + bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; @@ -680,10 +709,10 @@ struct PayChan_test : public beast::unit_test::suite Env env(*this, features - featureDepositAuth); env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), 3600s, alice.pk()), + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk()), ter(tecNO_TARGET)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } { // Create a channel where dst disallows XRP. Ignore that flag, @@ -691,9 +720,9 @@ struct PayChan_test : public beast::unit_test::suite Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), 3600s, alice.pk())); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk())); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); } { @@ -701,13 +730,13 @@ struct PayChan_test : public beast::unit_test::suite // (channel is created before disallow xrp is set) Env env(*this, features - featureDepositAuth); env.fund(XRP(10000), alice, bob); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), 3600s, alice.pk())); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk())); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env(fset(bob, asfDisallowXRP)); auto const reqBal = XRP(500).value(); - env(claim(alice, chan, reqBal, reqBal), ter(tecNO_TARGET)); + env(paychan::claim(alice, chan, reqBal, reqBal), ter(tecNO_TARGET)); } { // Claim to a channel where dst disallows XRP (channel is @@ -715,13 +744,13 @@ struct PayChan_test : public beast::unit_test::suite // since it is just advisory. Env env{*this, features}; env.fund(XRP(10000), alice, bob); - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), 3600s, alice.pk())); - BEAST_EXPECT(channelExists(*env.current(), chan)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk())); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env(fset(bob, asfDisallowXRP)); auto const reqBal = XRP(500).value(); - env(claim(alice, chan, reqBal, reqBal)); + env(paychan::claim(alice, chan, reqBal, reqBal)); } } @@ -742,16 +771,16 @@ struct PayChan_test : public beast::unit_test::suite auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); { - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk), + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk), ter(tecDST_TAG_NEEDED)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } { - auto const chan = channel(alice, bob, env.seq(alice)); - env(create( + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create( alice, bob, channelFunds, settleDelay, pk, std::nullopt, 1)); - BEAST_EXPECT(channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); } } @@ -775,20 +804,22 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == XRP(1000)); // alice can add more funds to the channel even though bob has // asfDepositAuth set. - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); env.close(); // alice claims. Fails because bob's lsfDepositAuth flag is set. - env(claim(alice, chan, XRP(500).value(), XRP(500).value()), + env(paychan::claim(alice, chan, XRP(500).value(), XRP(500).value()), ter(tecNO_PERMISSION)); env.close(); @@ -801,20 +832,21 @@ struct PayChan_test : public beast::unit_test::suite // alice claims with signature. Fails since bob has // lsfDepositAuth flag set. - env(claim(alice, chan, delta, delta, Slice(sig), pk), + env(paychan::claim(alice, chan, delta, delta, Slice(sig), pk), ter(tecNO_PERMISSION)); env.close(); BEAST_EXPECT(env.balance(bob) == preBob); // bob claims but omits the signature. Fails because only // alice can claim without a signature. - env(claim(bob, chan, delta, delta), ter(temBAD_SIGNATURE)); + env(paychan::claim(bob, chan, delta, delta), + ter(temBAD_SIGNATURE)); env.close(); // bob claims with signature. Succeeds even though bob's // lsfDepositAuth flag is set since bob submitted the // transaction. - env(claim(bob, chan, delta, delta, Slice(sig), pk)); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); BEAST_EXPECT(env.balance(bob) == preBob + delta - baseFee); } @@ -825,7 +857,7 @@ struct PayChan_test : public beast::unit_test::suite // carol claims and fails. Only channel participants (bob or // alice) may claim. - env(claim(carol, chan, delta, delta, Slice(sig), pk), + env(paychan::claim(carol, chan, delta, delta, Slice(sig), pk), ter(tecNO_PERMISSION)); env.close(); @@ -834,12 +866,12 @@ struct PayChan_test : public beast::unit_test::suite env(deposit::auth(bob, carol)); env.close(); - env(claim(carol, chan, delta, delta, Slice(sig), pk), + env(paychan::claim(carol, chan, delta, delta, Slice(sig), pk), ter(tecNO_PERMISSION)); // Since alice is not preauthorized she also may not claim // for bob. - env(claim(alice, chan, delta, delta, Slice(sig), pk), + env(paychan::claim(alice, chan, delta, delta, Slice(sig), pk), ter(tecNO_PERMISSION)); env.close(); @@ -848,7 +880,7 @@ struct PayChan_test : public beast::unit_test::suite env(deposit::auth(bob, alice)); env.close(); - env(claim(alice, chan, delta, delta, Slice(sig), pk)); + env(paychan::claim(alice, chan, delta, delta, Slice(sig), pk)); env.close(); BEAST_EXPECT( @@ -863,7 +895,8 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // alice claims and fails since she is no longer preauthorized. - env(claim(alice, chan, delta, delta), ter(tecNO_PERMISSION)); + env(paychan::claim(alice, chan, delta, delta), + ter(tecNO_PERMISSION)); env.close(); // bob clears lsfDepositAuth. Now alice can claim. @@ -871,7 +904,7 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // alice claims successfully. - env(claim(alice, chan, delta, delta)); + env(paychan::claim(alice, chan, delta, delta)); env.close(); BEAST_EXPECT( env.balance(bob) == preBob + XRP(800) - (5 * baseFee)); @@ -900,12 +933,12 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); // alice add funds to the channel - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); env.close(); std::string const credBadIdx = @@ -935,7 +968,7 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // Fail, credentials not accepted - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({credIdx}), ter(tecBAD_CREDENTIALS)); env.close(); @@ -944,7 +977,7 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // Fail, no depositPreauth object - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({credIdx}), ter(tecNO_PERMISSION)); env.close(); @@ -954,20 +987,21 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // Fail, credentials doesn’t belong to root account - env(claim(dillon, chan, delta, delta), + env(paychan::claim(dillon, chan, delta, delta), credentials::ids({credIdx}), ter(tecBAD_CREDENTIALS)); // Fails because bob's lsfDepositAuth flag is set. - env(claim(alice, chan, delta, delta), ter(tecNO_PERMISSION)); + env(paychan::claim(alice, chan, delta, delta), + ter(tecNO_PERMISSION)); // Fail, bad credentials index. - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({credBadIdx}), ter(tecBAD_CREDENTIALS)); // Fail, empty credentials - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({}), ter(temMALFORMED)); @@ -978,7 +1012,7 @@ struct PayChan_test : public beast::unit_test::suite for (int i = 0; i < 10; ++i) env.close(); - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({credIdx}), ter(tecEXPIRED)); env.close(); @@ -996,7 +1030,7 @@ struct PayChan_test : public beast::unit_test::suite jv[jss::result][jss::index].asString(); // Success - env(claim(alice, chan, delta, delta), + env(paychan::claim(alice, chan, delta, delta), credentials::ids({credIdx})); } } @@ -1007,12 +1041,12 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); // alice add funds to the channel - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); env.close(); auto const delta = XRP(500).value(); @@ -1029,7 +1063,8 @@ struct PayChan_test : public beast::unit_test::suite std::string const credIdx = jv[jss::result][jss::index].asString(); // Succeed, lsfDepositAuth is not set - env(claim(alice, chan, delta, delta), credentials::ids({credIdx})); + env(paychan::claim(alice, chan, delta, delta), + credentials::ids({credIdx})); env.close(); } @@ -1041,11 +1076,11 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); env.close(); std::string const credIdx = "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" @@ -1057,7 +1092,7 @@ struct PayChan_test : public beast::unit_test::suite env(deposit::auth(bob, alice)); env.close(); - env(claim(alice, chan, XRP(500).value(), XRP(500).value()), + env(paychan::claim(alice, chan, XRP(500).value(), XRP(500).value()), credentials::ids({credIdx}), ter(temDISABLED)); } @@ -1077,12 +1112,12 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1 = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk)); - BEAST_EXPECT(channelExists(*env.current(), chan1)); - auto const chan2 = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, channelFunds, settleDelay, pk)); - BEAST_EXPECT(channelExists(*env.current(), chan2)); + auto const chan1 = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan1)); + auto const chan2 = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan2)); BEAST_EXPECT(chan1 != chan2); } @@ -1101,8 +1136,9 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan1Str = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { // test account non-string @@ -1144,8 +1180,9 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(r[jss::result][jss::channels].size() == 0); BEAST_EXPECT(r[jss::result][jss::validated]); } - auto const chan2Str = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan2Str = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { auto const r = @@ -1194,7 +1231,8 @@ struct PayChan_test : public beast::unit_test::suite auto const channelFunds = XRP(1); for (auto const& b : bobs) { - env(create(alice, b, channelFunds, settleDelay, alice.pk())); + env(paychan::create( + alice, b, channelFunds, settleDelay, alice.pk())); } } @@ -1296,8 +1334,8 @@ struct PayChan_test : public beast::unit_test::suite // channels where alice is the source, not the destination auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - env(create(alice, bob, channelFunds, settleDelay, alice.pk())); - env(create(bob, alice, channelFunds, settleDelay, bob.pk())); + env(paychan::create(alice, bob, channelFunds, settleDelay, alice.pk())); + env(paychan::create(bob, alice, channelFunds, settleDelay, bob.pk())); auto const r = [&] { Json::Value jvc; @@ -1327,8 +1365,9 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan1Str = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); Json::Value args{Json::objectValue}; @@ -1365,8 +1404,9 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan1Str = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); std::string chan1PkStr; { @@ -1394,8 +1434,9 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(r[jss::result][jss::channels].size() == 0); BEAST_EXPECT(r[jss::result][jss::validated]); } - auto const chan2Str = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan2Str = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { auto const r = @@ -1532,8 +1573,8 @@ struct PayChan_test : public beast::unit_test::suite { // Try to explicitly specify secp256k1 and Ed25519 keys: auto const chan = - to_string(channel(charlie, alice, env.seq(charlie))); - env(create( + to_string(paychan::channel(charlie, alice, env.seq(charlie))); + env(paychan::create( charlie, alice, channelFunds, settleDelay, charlie.pk())); env.close(); @@ -1710,8 +1751,9 @@ struct PayChan_test : public beast::unit_test::suite std::optional cancelAfter; { - auto const chan = to_string(channel(alice, bob, env.seq(alice))); - env(create(alice, bob, channelFunds, settleDelay, pk)); + auto const chan = + to_string(paychan::channel(alice, bob, env.seq(alice))); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); auto const r = env.rpc("account_channels", alice.human(), bob.human()); BEAST_EXPECT(r[jss::result][jss::channels].size() == 1); @@ -1722,8 +1764,9 @@ struct PayChan_test : public beast::unit_test::suite } { std::uint32_t dstTag = 42; - auto const chan = to_string(channel(alice, carol, env.seq(alice))); - env(create( + auto const chan = + to_string(paychan::channel(alice, carol, env.seq(alice))); + env(paychan::create( alice, carol, channelFunds, @@ -1756,8 +1799,8 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - auto jv = create(alice, bob, XRP(1000), settleDelay, pk); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + auto jv = paychan::create(alice, bob, XRP(1000), settleDelay, pk); auto const pkHex = strHex(pk.slice()); jv["PublicKey"] = pkHex.substr(2, pkHex.size() - 2); env(jv, ter(temMALFORMED)); @@ -1774,7 +1817,7 @@ struct PayChan_test : public beast::unit_test::suite auto const authAmt = XRP(100); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - jv = claim( + jv = paychan::claim( bob, chan, authAmt.value(), @@ -1844,7 +1887,7 @@ struct PayChan_test : public beast::unit_test::suite // directory Env env(*this, features - fixPayChanRecipientOwnerDir); env.fund(XRP(10000), alice, bob); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); @@ -1853,8 +1896,8 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); // close the channel - env(claim(bob, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(bob, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); @@ -1865,7 +1908,7 @@ struct PayChan_test : public beast::unit_test::suite // Test with adding the paychan to the recipient's owner directory Env env{*this, features}; env.fund(XRP(10000), alice, bob); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); @@ -1874,8 +1917,8 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(inOwnerDir(*env.current(), bob, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); // close the channel - env(claim(bob, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(bob, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); @@ -1888,7 +1931,7 @@ struct PayChan_test : public beast::unit_test::suite Env env(*this, features - fixPayChanRecipientOwnerDir); env.fund(XRP(10000), alice, bob); // create the channel before the amendment activates - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); @@ -1909,8 +1952,8 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); // close the channel after the amendment activates - env(claim(bob, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(bob, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); @@ -1962,11 +2005,13 @@ struct PayChan_test : public beast::unit_test::suite // Create a channel from alice to bob auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == XRP(1000)); rmAccount(env, alice, carol, tecHAS_OBLIGATIONS); // can only remove bob if the channel isn't in their owner direcotry @@ -1977,8 +2022,8 @@ struct PayChan_test : public beast::unit_test::suite withOwnerDirFix ? TER(tecHAS_OBLIGATIONS) : TER(tesSUCCESS)); auto const feeDrops = env.current()->fees().base; - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); BEAST_EXPECT(chanBal == XRP(0)); BEAST_EXPECT(chanAmt == XRP(1000)); @@ -1991,20 +2036,25 @@ struct PayChan_test : public beast::unit_test::suite // claim should fail if the dst was removed if (withOwnerDirFix) { - env(claim(alice, chan, reqBal, authAmt)); + env(paychan::claim(alice, chan, reqBal, authAmt)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } else { auto const preAlice = env.balance(alice); - env(claim(alice, chan, reqBal, authAmt), ter(tecNO_DST)); + env(paychan::claim(alice, chan, reqBal, authAmt), + ter(tecNO_DST)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); } @@ -2013,34 +2063,36 @@ struct PayChan_test : public beast::unit_test::suite if (withOwnerDirFix) { auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); env.close(); BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1000) - feeDrops); BEAST_EXPECT( - channelAmount(*env.current(), chan) == chanAmt + XRP(1000)); + paychan::channelAmount(*env.current(), chan) == + chanAmt + XRP(1000)); chanAmt = chanAmt + XRP(1000); } else { auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ter(tecNO_DST)); + env(paychan::fund(alice, chan, XRP(1000)), ter(tecNO_DST)); env.close(); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); } { // Owner closes, will close after settleDelay - env(claim(alice, chan), txflags(tfClose)); + env(paychan::claim(alice, chan), txflags(tfClose)); env.close(); // settle delay hasn't ellapsed. Channels should exist. - BEAST_EXPECT(channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); auto const closeTime = env.current()->info().parentCloseTime; auto const minExpiration = closeTime + settleDelay; env.close(minExpiration); - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } } @@ -2053,19 +2105,21 @@ struct PayChan_test : public beast::unit_test::suite // Create a channel from alice to bob auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == XRP(1000)); // Since `fixPayChanRecipientOwnerDir` is not active, can remove bob rmAccount(env, bob, carol); BEAST_EXPECT(!env.closed()->exists(keylet::account(bob.id()))); auto const feeDrops = env.current()->fees().base; - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); BEAST_EXPECT(chanBal == XRP(0)); BEAST_EXPECT(chanAmt == XRP(1000)); auto preBob = env.balance(bob); @@ -2077,10 +2131,13 @@ struct PayChan_test : public beast::unit_test::suite { // claim should fail, since bob doesn't exist auto const preAlice = env.balance(alice); - env(claim(alice, chan, reqBal, authAmt), ter(tecNO_DST)); + env(paychan::claim(alice, chan, reqBal, authAmt), + ter(tecNO_DST)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); } @@ -2088,10 +2145,11 @@ struct PayChan_test : public beast::unit_test::suite { // fund should fail, sincebob doesn't exist auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ter(tecNO_DST)); + env(paychan::fund(alice, chan, XRP(1000)), ter(tecNO_DST)); env.close(); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); } // resurrect bob @@ -2104,9 +2162,11 @@ struct PayChan_test : public beast::unit_test::suite preBob = env.balance(bob); reqBal = chanBal + delta; authAmt = reqBal + XRP(100); - env(claim(alice, chan, reqBal, authAmt)); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::claim(alice, chan, reqBal, authAmt)); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } @@ -2118,9 +2178,12 @@ struct PayChan_test : public beast::unit_test::suite authAmt = reqBal + XRP(100); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; } @@ -2128,25 +2191,26 @@ struct PayChan_test : public beast::unit_test::suite { // alice should be able to fund auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000))); + env(paychan::fund(alice, chan, XRP(1000))); BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1000) - feeDrops); BEAST_EXPECT( - channelAmount(*env.current(), chan) == chanAmt + XRP(1000)); + paychan::channelAmount(*env.current(), chan) == + chanAmt + XRP(1000)); chanAmt = chanAmt + XRP(1000); } { // Owner closes, will close after settleDelay - env(claim(alice, chan), txflags(tfClose)); + env(paychan::claim(alice, chan), txflags(tfClose)); env.close(); // settle delay hasn't ellapsed. Channels should exist. - BEAST_EXPECT(channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); auto const closeTime = env.current()->info().parentCloseTime; auto const minExpiration = closeTime + settleDelay; env.close(minExpiration); - env(claim(alice, chan), txflags(tfClose)); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + env(paychan::claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); } } } @@ -2176,20 +2240,21 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - auto const chan = channel(alice, bob, aliceTicketSeq); + auto const chan = paychan::channel(alice, bob, aliceTicketSeq); - env(create(alice, bob, XRP(1000), settleDelay, pk), + env(paychan::create(alice, bob, XRP(1000), settleDelay, pk), ticket::use(aliceTicketSeq++)); env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000)); { auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ticket::use(aliceTicketSeq++)); + env(paychan::fund(alice, chan, XRP(1000)), + ticket::use(aliceTicketSeq++)); env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); @@ -2198,8 +2263,8 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops); } - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); + auto chanBal = paychan::channelBalance(*env.current(), chan); + auto chanAmt = paychan::channelAmount(*env.current(), chan); BEAST_EXPECT(chanBal == XRP(0)); BEAST_EXPECT(chanAmt == XRP(2000)); @@ -2210,14 +2275,16 @@ struct PayChan_test : public beast::unit_test::suite auto const reqBal = chanBal + delta; auto const authAmt = reqBal + XRP(100); assert(reqBal <= chanAmt); - env(claim(alice, chan, reqBal, authAmt), + env(paychan::claim(alice, chan, reqBal, authAmt), ticket::use(aliceTicketSeq++)); env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } @@ -2230,14 +2297,17 @@ struct PayChan_test : public beast::unit_test::suite assert(reqBal <= chanAmt); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ticket::use(bobTicketSeq++)); env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; @@ -2245,15 +2315,18 @@ struct PayChan_test : public beast::unit_test::suite // claim again preBob = env.balance(bob); // A transaction that generates a tec still consumes its ticket. - env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), + env(paychan::claim( + bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ticket::use(bobTicketSeq++), ter(tecUNFUNDED_PAYMENT)); env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); } { @@ -2266,41 +2339,44 @@ struct PayChan_test : public beast::unit_test::suite // the ticket is not consumed. So we don't increment bobTicket. auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - env(claim(bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()), + env(paychan::claim( + bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()), ticket::use(bobTicketSeq), ter(temBAD_AMOUNT)); env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT( + paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob); } // Dst tries to fund the channel - env(fund(bob, chan, XRP(1000)), + env(paychan::fund(bob, chan, XRP(1000)), ticket::use(bobTicketSeq++), ter(tecNO_PERMISSION)); env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); { // Dst closes channel auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - env(claim(bob, chan), + env(paychan::claim(bob, chan), txflags(tfClose), ticket::use(bobTicketSeq++)); env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); auto const feeDrops = env.current()->fees().base; auto const delta = chanAmt - chanBal; assert(delta > beast::zero); @@ -2347,6 +2423,7 @@ struct PayChan_test : public beast::unit_test::suite FeatureBitset const all{testable_amendments()}; testWithFeats(all - disallowIncoming); testWithFeats(all); + testWithFeats(all - featureTokenEscrow); testDepositAuthCreds(); } }; diff --git a/src/test/jtx.h b/src/test/jtx.h index 6347b9dcf98..0d66ea0b021 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index d4a39b64985..16698324fe3 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -111,6 +111,23 @@ checkArraySize(Json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +/* Token (IOU/MPT) Locking */ +/******************************************************************************/ +uint64_t +mptEscrowed( + jtx::Env const& env, + jtx::Account const& account, + jtx::MPT const& mpt); + +uint64_t +issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt); + +jtx::PrettyAmount +issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue); + +jtx::PrettyAmount +issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue); + /* Path finding */ /******************************************************************************/ void @@ -233,67 +250,6 @@ expectLedgerEntryRoot( Account const& acct, STAmount const& expectedValue); -/* Payment Channel */ -/******************************************************************************/ - -Json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt); - -inline Json::Value -create( - Account const& account, - Account const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt) -{ - return create( - account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); -} - -Json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration = std::nullopt); - -Json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance = std::nullopt, - std::optional const& amount = std::nullopt, - std::optional const& signature = std::nullopt, - std::optional const& pk = std::nullopt); - -uint256 -channel( - AccountID const& account, - AccountID const& dst, - std::uint32_t seqProxyValue); - -inline uint256 -channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) -{ - return channel(account.id(), dst.id(), seqProxyValue); -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan); - -bool -channelExists(ReadView const& view, uint256 const& chan); - /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 5f8c53877ac..f767940635d 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -56,6 +56,58 @@ ownerCount(Env const& env, Account const& account) return env.ownerCount(account); } +/* Token (IOU/MPT) Locking */ +/******************************************************************************/ +uint64_t +mptEscrowed( + jtx::Env const& env, + jtx::Account const& account, + jtx::MPT const& mpt) +{ + auto const sle = env.le(keylet::mptoken(mpt.mpt(), account)); + if (sle && sle->isFieldPresent(sfLockedAmount)) + return (*sle)[sfLockedAmount]; + return 0; +} + +uint64_t +issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt) +{ + auto const sle = env.le(keylet::mptIssuance(mpt.mpt())); + if (sle && sle->isFieldPresent(sfLockedAmount)) + return (*sle)[sfLockedAmount]; + return 0; +} + +jtx::PrettyAmount +issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue) +{ + Json::Value params; + params[jss::account] = account.human(); + auto jrr = env.rpc("json", "gateway_balances", to_string(params)); + auto const result = jrr[jss::result]; + auto const obligations = + result[jss::obligations][to_string(issue.currency)]; + if (obligations.isNull()) + return {STAmount(issue, 0), account.name()}; + STAmount const amount = amountFromString(issue, obligations.asString()); + return {amount, account.name()}; +} + +jtx::PrettyAmount +issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue) +{ + Json::Value params; + params[jss::account] = account.human(); + auto jrr = env.rpc("json", "gateway_balances", to_string(params)); + auto const result = jrr[jss::result]; + auto const locked = result[jss::locked][to_string(issue.currency)]; + if (locked.isNull()) + return {STAmount(issue, 0), account.name()}; + STAmount const amount = amountFromString(issue, locked.asString()); + return {amount, account.name()}; +} + /* Path finding */ /******************************************************************************/ void @@ -211,99 +263,6 @@ expectLedgerEntryRoot( return accountBalance(env, acct) == to_string(expectedValue.xrp()); } -/* Payment Channel */ -/******************************************************************************/ -Json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter, - std::optional const& dstTag) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Account] = to_string(account); - jv[jss::Destination] = to_string(to); - jv[jss::Amount] = amount.getJson(JsonOptions::none); - jv[jss::SettleDelay] = settleDelay.count(); - jv[sfPublicKey.fieldName] = strHex(pk.slice()); - if (cancelAfter) - jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count(); - if (dstTag) - jv[sfDestinationTag.fieldName] = *dstTag; - return jv; -} - -Json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelFund; - jv[jss::Account] = to_string(account); - jv[sfChannel.fieldName] = to_string(channel); - jv[jss::Amount] = amount.getJson(JsonOptions::none); - if (expiration) - jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); - return jv; -} - -Json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance, - std::optional const& amount, - std::optional const& signature, - std::optional const& pk) -{ - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelClaim; - jv[jss::Account] = to_string(account); - jv["Channel"] = to_string(channel); - if (amount) - jv[jss::Amount] = amount->getJson(JsonOptions::none); - if (balance) - jv["Balance"] = balance->getJson(JsonOptions::none); - if (signature) - jv["Signature"] = strHex(*signature); - if (pk) - jv["PublicKey"] = strHex(pk->slice()); - return jv; -} - -uint256 -channel( - AccountID const& account, - AccountID const& dst, - std::uint32_t seqProxyValue) -{ - auto const k = keylet::payChan(account, dst, seqProxyValue); - return k.key; -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - if (!slep) - return XRPAmount{-1}; - return (*slep)[sfBalance]; -} - -bool -channelExists(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - return bool(slep); -} - /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/paychan.cpp b/src/test/jtx/impl/paychan.cpp new file mode 100644 index 00000000000..a44fa5acf62 --- /dev/null +++ b/src/test/jtx/impl/paychan.cpp @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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 + +namespace ripple { +namespace test { +namespace jtx { + +/** Paychan operations. */ +namespace paychan { + +Json::Value +create( + AccountID const& account, + AccountID const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter, + std::optional const& dstTag) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Flags] = tfFullyCanonicalSig; + jv[jss::Account] = to_string(account); + jv[jss::Destination] = to_string(to); + jv[jss::Amount] = amount.getJson(JsonOptions::none); + jv[jss::SettleDelay] = settleDelay.count(); + jv[sfPublicKey.fieldName] = strHex(pk.slice()); + if (cancelAfter) + jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count(); + if (dstTag) + jv[sfDestinationTag.fieldName] = *dstTag; + return jv; +} + +Json::Value +fund( + AccountID const& account, + uint256 const& channel, + STAmount const& amount, + std::optional const& expiration) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelFund; + jv[jss::Flags] = tfFullyCanonicalSig; + jv[jss::Account] = to_string(account); + jv[sfChannel.fieldName] = to_string(channel); + jv[jss::Amount] = amount.getJson(JsonOptions::none); + if (expiration) + jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); + return jv; +} + +Json::Value +claim( + AccountID const& account, + uint256 const& channel, + std::optional const& balance, + std::optional const& amount, + std::optional const& signature, + std::optional const& pk) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelClaim; + jv[jss::Flags] = tfFullyCanonicalSig; + jv[jss::Account] = to_string(account); + jv["Channel"] = to_string(channel); + if (amount) + jv[jss::Amount] = amount->getJson(JsonOptions::none); + if (balance) + jv["Balance"] = balance->getJson(JsonOptions::none); + if (signature) + jv["Signature"] = strHex(*signature); + if (pk) + jv["PublicKey"] = strHex(pk->slice()); + return jv; +} + +uint256 +channel( + AccountID const& account, + AccountID const& dst, + std::uint32_t seqProxyValue) +{ + auto const k = keylet::payChan(account, dst, seqProxyValue); + return k.key; +} + +STAmount +channelBalance(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + if (!slep) + return XRPAmount{-1}; + return (*slep)[sfBalance]; +} + +STAmount +channelAmount(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + if (!slep) + return XRPAmount{-1}; + return (*slep)[sfAmount]; +} + +bool +channelExists(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + return bool(slep); +} + +Buffer +signClaimAuth( + PublicKey const& pk, + SecretKey const& sk, + uint256 const& channel, + STAmount const& authAmt) +{ + Serializer msg; + serializePayChanAuthorization(msg, channel, authAmt); + return sign(pk, sk, msg.slice()); +} + +Rate +rate( + Env& env, + Account const& account, + Account const& dest, + std::uint32_t const& seq) +{ + auto const sle = env.le(keylet::payChan(account.id(), dest.id(), seq)); + if (sle->isFieldPresent(sfTransferRate)) + return ripple::Rate((*sle)[sfTransferRate]); + return Rate{0}; +} + +} // namespace paychan + +} // namespace jtx + +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/paychan.h b/src/test/jtx/paychan.h new file mode 100644 index 00000000000..cf6214d6883 --- /dev/null +++ b/src/test/jtx/paychan.h @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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_PAYCHAN_H_INCLUDED +#define RIPPLE_TEST_JTX_PAYCHAN_H_INCLUDED + +#include +#include +#include +#include + +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** Paychan operations. */ +namespace paychan { + +Json::Value +create( + AccountID const& account, + AccountID const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter = std::nullopt, + std::optional const& dstTag = std::nullopt); + +inline Json::Value +create( + Account const& account, + Account const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter = std::nullopt, + std::optional const& dstTag = std::nullopt) +{ + return create( + account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); +} + +Json::Value +fund( + AccountID const& account, + uint256 const& channel, + STAmount const& amount, + std::optional const& expiration = std::nullopt); + +Json::Value +claim( + AccountID const& account, + uint256 const& channel, + std::optional const& balance = std::nullopt, + std::optional const& amount = std::nullopt, + std::optional const& signature = std::nullopt, + std::optional const& pk = std::nullopt); + +uint256 +channel( + AccountID const& account, + AccountID const& dst, + std::uint32_t seqProxyValue); + +inline uint256 +channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) +{ + return channel(account.id(), dst.id(), seqProxyValue); +} + +STAmount +channelBalance(ReadView const& view, uint256 const& chan); + +STAmount +channelAmount(ReadView const& view, uint256 const& chan); + +bool +channelExists(ReadView const& view, uint256 const& chan); + +Buffer +signClaimAuth( + PublicKey const& pk, + SecretKey const& sk, + uint256 const& channel, + STAmount const& authAmt); + +Rate +rate( + Env& env, + Account const& account, + Account const& dest, + std::uint32_t const& seq); + +} // namespace paychan + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index c824eccfba6..fea2579c939 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -305,8 +305,8 @@ class ApplicationImp : public Application, public BasicApp static_cast(std::thread::hardware_concurrency()); // Be more aggressive about the number of threads to use - // for the job queue if the server is configured as "large" - // or "huge" if there are enough cores. + // for the job queue if the server is configured as + // "large" or "huge" if there are enough cores. if (config->NODE_SIZE >= 4 && count >= 16) count = 6 + std::min(count, 8); else if (config->NODE_SIZE >= 3 && count >= 8) diff --git a/src/xrpld/app/misc/EscrowUtils.h b/src/xrpld/app/misc/EscrowUtils.h new file mode 100644 index 00000000000..8c80fff3761 --- /dev/null +++ b/src/xrpld/app/misc/EscrowUtils.h @@ -0,0 +1,580 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 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_APP_MISC_ESCROWUTILS_H_INLCUDED +#define RIPPLE_APP_MISC_ESCROWUTILS_H_INLCUDED + +#include +#include + +#include +#include +#include +#include + +namespace ripple { + +template +static NotTEC +createPreflightHelper(PreflightContext const& ctx); + +template <> +NotTEC +createPreflightHelper(PreflightContext const& ctx) +{ + STAmount const amount = ctx.tx[sfAmount]; + if (amount.native() || amount <= beast::zero) + return temBAD_AMOUNT; + + if (badCurrency() == amount.getCurrency()) + return temBAD_CURRENCY; + + return tesSUCCESS; +} + +template <> +NotTEC +createPreflightHelper(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + auto const amount = ctx.tx[sfAmount]; + if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || + amount <= beast::zero) + return temBAD_AMOUNT; + + return tesSUCCESS; +} + +template +static TER +createPreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount); + +template <> +TER +createPreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the account, return tecNO_PERMISSION + if (issuer == account) + return tecNO_PERMISSION; + + // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION + auto const sleIssuer = ctx.view.read(keylet::account(issuer)); + if (!sleIssuer) + return tecNO_ISSUER; + if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) + return tecNO_PERMISSION; + + // If the account does not have a trustline to the issuer, return tecNO_LINE + auto const sleRippleState = + ctx.view.read(keylet::line(account, issuer, amount.getCurrency())); + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + // If balance is positive, issuer must have higher address than account + if (balance > beast::zero && issuer < account) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If balance is negative, issuer must have lower address than account + if (balance < beast::zero && issuer > account) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If the issuer has requireAuth set, check if the account is authorized + if (auto const ter = requireAuth(ctx.view, amount.issue(), account); + ter != tesSUCCESS) + return ter; + + // If the issuer has requireAuth set, check if the destination is authorized + if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); + ter != tesSUCCESS) + return ter; + + // If the issuer has frozen the account, return tecFROZEN + if (isFrozen(ctx.view, account, amount.issue())) + return tecFROZEN; + + // If the issuer has frozen the destination, return tecFROZEN + if (isFrozen(ctx.view, dest, amount.issue())) + return tecFROZEN; + + STAmount const spendableAmount = accountHolds( + ctx.view, + account, + amount.getCurrency(), + issuer, + fhIGNORE_FREEZE, + ctx.j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return + // tecINSUFFICIENT_FUNDS + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + // If the amount is not addable to the balance, return tecPRECISION_LOSS + if (!canAdd(spendableAmount, amount)) + return tecPRECISION_LOSS; + + return tesSUCCESS; +} + +template <> +TER +createPreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the account, return tecNO_PERMISSION + if (issuer == account) + return tecNO_PERMISSION; + + // If the mpt does not exist, return tecOBJECT_NOT_FOUND + auto const issuanceKey = + keylet::mptIssuance(amount.get().getMptID()); + auto const sleIssuance = ctx.view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION + if (!sleIssuance->isFlag(lsfMPTCanEscrow)) + return tecNO_PERMISSION; + + // If the issuer is not the same as the issuer of the mpt, return + // tecNO_PERMISSION + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If the account does not have the mpt, return tecOBJECT_NOT_FOUND + if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account))) + return tecOBJECT_NOT_FOUND; + + // If the issuer has requireAuth set, check if the account is + // authorized + auto const& mptIssue = amount.get(); + if (auto const ter = + requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + + // If the issuer has requireAuth set, check if the destination is + // authorized + if (auto const ter = + requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + + // If the issuer has frozen the account, return tecLOCKED + if (isFrozen(ctx.view, account, mptIssue)) + return tecLOCKED; + + // If the issuer has frozen the destination, return tecLOCKED + if (isFrozen(ctx.view, dest, mptIssue)) + return tecLOCKED; + + // If the mpt cannot be transferred, return tecNO_AUTH + if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); + ter != tesSUCCESS) + return ter; + + STAmount const spendableAmount = accountHolds( + ctx.view, + account, + amount.get(), + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + ctx.j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return + // tecINSUFFICIENT_FUNDS + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +template +static TER +escrowLockApplyHelper( + ApplyView& view, + AccountID const& issuer, + AccountID const& sender, + STAmount const& amount, + beast::Journal journal); + +template <> +TER +escrowLockApplyHelper( + ApplyView& view, + AccountID const& issuer, + AccountID const& sender, + STAmount const& amount, + beast::Journal journal) +{ + // Defensive: Issuer cannot create an escrow + // LCOV_EXCL_START + if (issuer == sender) + return tecINTERNAL; + // LCOV_EXCL_STOP + + auto const ter = rippleCredit( + view, + sender, + issuer, + amount, + amount.holds() ? false : true, + journal); + if (ter != tesSUCCESS) + return ter; // LCOV_EXCL_LINE + return tesSUCCESS; +} + +template <> +TER +escrowLockApplyHelper( + ApplyView& view, + AccountID const& issuer, + AccountID const& sender, + STAmount const& amount, + beast::Journal journal) +{ + // Defensive: Issuer cannot create an escrow + // LCOV_EXCL_START + if (issuer == sender) + return tecINTERNAL; + // LCOV_EXCL_STOP + + auto const ter = rippleLockEscrowMPT(view, sender, amount, journal); + if (ter != tesSUCCESS) + return ter; // LCOV_EXCL_LINE + return tesSUCCESS; +} + +template +static TER +escrowUnlockPreclaimHelper( + ReadView const& view, + AccountID const& account, + STAmount const& amount, + bool checkFreeze = true); + +template <> +TER +escrowUnlockPreclaimHelper( + ReadView const& view, + AccountID const& account, + STAmount const& amount, + bool checkFreeze) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the account, return tesSUCCESS + if (issuer == account) + return tesSUCCESS; + + // If the issuer has requireAuth set, check if the destination is authorized + if (auto const ter = requireAuth(view, amount.issue(), account); + ter != tesSUCCESS) + return ter; + + // If the issuer has deep frozen the account, return tecFROZEN + if (checkFreeze && + isDeepFrozen(view, account, amount.getCurrency(), amount.getIssuer())) + return tecFROZEN; + + return tesSUCCESS; +} + +template <> +TER +escrowUnlockPreclaimHelper( + ReadView const& view, + AccountID const& account, + STAmount const& amount, + bool checkFreeze) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the account, return tesSUCCESS + if (issuer == account) + return tesSUCCESS; + + // If the mpt does not exist, return tecOBJECT_NOT_FOUND + auto const issuanceKey = + keylet::mptIssuance(amount.get().getMptID()); + auto const sleIssuance = view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + // If the issuer has requireAuth set, check if the account is + // authorized + auto const& mptIssue = amount.get(); + if (auto const ter = + requireAuth(view, mptIssue, account, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + + // If the issuer has frozen the account, return tecLOCKED + if (checkFreeze && isFrozen(view, account, mptIssue)) + return tecLOCKED; + + return tesSUCCESS; +} + +template +static TER +escrowUnlockApplyHelper( + ApplyView& view, + Rate lockedRate, + std::shared_ptr const& sleDest, + STAmount const& xrpBalance, + STAmount const& amount, + AccountID const& issuer, + AccountID const& sender, + AccountID const& receiver, + bool createAsset, + beast::Journal journal); + +template <> +TER +escrowUnlockApplyHelper( + ApplyView& view, + Rate lockedRate, + std::shared_ptr const& sleDest, + STAmount const& xrpBalance, + STAmount const& amount, + AccountID const& issuer, + AccountID const& sender, + AccountID const& receiver, + bool createAsset, + beast::Journal journal) +{ + Keylet const trustLineKey = keylet::line(receiver, amount.issue()); + bool const recvLow = issuer > receiver; + bool const senderIssuer = issuer == sender; + bool const receiverIssuer = issuer == receiver; + bool const issuerHigh = issuer > receiver; + + // LCOV_EXCL_START + if (senderIssuer) + return tecINTERNAL; + // LCOV_EXCL_STOP + + if (receiverIssuer) + return tesSUCCESS; + + if (!view.exists(trustLineKey) && createAsset && !receiverIssuer) + { + // Can the account cover the trust line's reserve? + if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; + xrpBalance < view.fees().accountReserve(ownerCount + 1)) + { + JLOG(journal.trace()) << "Trust line does not exist. " + "Insufficent reserve to create line."; + + return tecNO_LINE_INSUF_RESERVE; + } + + Currency const currency = amount.getCurrency(); + STAmount initialBalance(amount.issue()); + initialBalance.setIssuer(noAccount()); + + // clang-format off + if (TER const ter = trustCreate( + view, // payment sandbox + recvLow, // is dest low? + issuer, // source + receiver, // destination + trustLineKey.key, // ledger index + sleDest, // Account to add to + false, // authorize account + (sleDest->getFlags() & lsfDefaultRipple) == 0, + false, // freeze trust line + false, // deep freeze trust line + initialBalance, // zero initial balance + Issue(currency, receiver), // limit of zero + 0, // quality in + 0, // quality out + journal); // journal + !isTesSuccess(ter)) + { + return ter; // LCOV_EXCL_LINE + } + // clang-format on + + view.update(sleDest); + } + + if (!view.exists(trustLineKey) && !receiverIssuer) + return tecNO_LINE; + + auto const xferRate = transferRate(view, amount); + // update if issuer rate is less than locked rate + if (xferRate < lockedRate) + lockedRate = xferRate; + + // Transfer Rate only applies when: + // 1. Issuer is not involved in the transfer (senderIssuer or + // receiverIssuer) + // 2. The locked rate is different from the parity rate + + // NOTE: Transfer fee in escrow works a bit differently from a normal + // payment. In escrow, the fee is deducted from the locked/sending amount, + // whereas in a normal payment, the transfer fee is taken on top of the + // sending amount. + auto finalAmt = amount; + if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) + { + // compute transfer fee, if any + auto const xferFee = amount.value() - + divideRound(amount, lockedRate, amount.issue(), true); + // compute balance to transfer + finalAmt = amount.value() - xferFee; + } + + // validate the line limit if the account submitting txn is not the receiver + // of the funds + if (!createAsset) + { + auto const sleRippleState = view.peek(trustLineKey); + if (!sleRippleState) + return tecINTERNAL; // LCOV_EXCL_LINE + + // if the issuer is the high, then we use the low limit + // otherwise we use the high limit + STAmount const lineLimit = sleRippleState->getFieldAmount( + issuerHigh ? sfLowLimit : sfHighLimit); + + STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance); + + // flip the sign of the line balance if the issuer is not high + if (!issuerHigh) + lineBalance.negate(); + + // add the final amount to the line balance + lineBalance += finalAmt; + + // if the transfer would exceed the line limit return tecLIMIT_EXCEEDED + if (lineLimit < lineBalance) + return tecLIMIT_EXCEEDED; + } + + // if destination is not the issuer then transfer funds + if (!receiverIssuer) + { + auto const ter = + rippleCredit(view, issuer, receiver, finalAmt, true, journal); + if (ter != tesSUCCESS) + return ter; // LCOV_EXCL_LINE + } + return tesSUCCESS; +} + +template <> +TER +escrowUnlockApplyHelper( + ApplyView& view, + Rate lockedRate, + std::shared_ptr const& sleDest, + STAmount const& xrpBalance, + STAmount const& amount, + AccountID const& issuer, + AccountID const& sender, + AccountID const& receiver, + bool createAsset, + beast::Journal journal) +{ + bool const senderIssuer = issuer == sender; + bool const receiverIssuer = issuer == receiver; + + auto const mptID = amount.get().getMptID(); + auto const issuanceKey = keylet::mptIssuance(mptID); + if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && + createAsset && !receiverIssuer) + { + if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; + xrpBalance < view.fees().accountReserve(ownerCount + 1)) + { + return tecINSUFFICIENT_RESERVE; + } + + if (auto const ter = + MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); + !isTesSuccess(ter)) + { + return ter; // LCOV_EXCL_LINE + } + + // update owner count. + adjustOwnerCount(view, sleDest, 1, journal); + } + + if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && + !receiverIssuer) + return tecNO_PERMISSION; + + auto const xferRate = transferRate(view, amount); + // update if issuer rate is less than locked rate + if (xferRate < lockedRate) + lockedRate = xferRate; + + // Transfer Rate only applies when: + // 1. Issuer is not involved in the transfer (senderIssuer or + // receiverIssuer) + // 2. The locked rate is different from the parity rate + + // NOTE: Transfer fee in escrow works a bit differently from a normal + // payment. In escrow, the fee is deducted from the locked/sending amount, + // whereas in a normal payment, the transfer fee is taken on top of the + // sending amount. + auto finalAmt = amount; + if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) + { + // compute transfer fee, if any + auto const xferFee = amount.value() - + divideRound(amount, lockedRate, amount.asset(), true); + // compute balance to transfer + finalAmt = amount.value() - xferFee; + } + + return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal); +} + +} // namespace ripple + +#endif // RIPPLE_APP_MISC_ESCROWUTILS_H_INLCUDED diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index dd0ffac7787..023e50d0302 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -85,39 +86,6 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx) ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero}; } -template -static NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx); - -template <> -NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx) -{ - STAmount const amount = ctx.tx[sfAmount]; - if (amount.native() || amount <= beast::zero) - return temBAD_AMOUNT; - - if (badCurrency() == amount.getCurrency()) - return temBAD_CURRENCY; - - return tesSUCCESS; -} - -template <> -NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureMPTokensV1)) - return temDISABLED; - - auto const amount = ctx.tx[sfAmount]; - if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || - amount <= beast::zero) - return temBAD_AMOUNT; - - return tesSUCCESS; -} - NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -135,7 +103,7 @@ EscrowCreate::preflight(PreflightContext const& ctx) if (auto const ret = std::visit( [&](T const&) { - return escrowCreatePreflightHelper(ctx); + return createPreflightHelper(ctx); }, amount.asset().value()); !isTesSuccess(ret)) @@ -192,173 +160,6 @@ EscrowCreate::preflight(PreflightContext const& ctx) return preflight2(ctx); } -template -static TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount); - -template <> -TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION - auto const sleIssuer = ctx.view.read(keylet::account(issuer)); - if (!sleIssuer) - return tecNO_ISSUER; - if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) - return tecNO_PERMISSION; - - // If the account does not have a trustline to the issuer, return tecNO_LINE - auto const sleRippleState = - ctx.view.read(keylet::line(account, issuer, amount.getCurrency())); - if (!sleRippleState) - return tecNO_LINE; - - STAmount const balance = (*sleRippleState)[sfBalance]; - - // If balance is positive, issuer must have higher address than account - if (balance > beast::zero && issuer < account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If balance is negative, issuer must have lower address than account - if (balance < beast::zero && issuer > account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), account); - ter != tesSUCCESS) - return ter; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the account, return tecFROZEN - if (isFrozen(ctx.view, account, amount.issue())) - return tecFROZEN; - - // If the issuer has frozen the destination, return tecFROZEN - if (isFrozen(ctx.view, dest, amount.issue())) - return tecFROZEN; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.getCurrency(), - issuer, - fhIGNORE_FREEZE, - ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::zero) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - // If the amount is not addable to the balance, return tecPRECISION_LOSS - if (!canAdd(spendableAmount, amount)) - return tecPRECISION_LOSS; - - return tesSUCCESS; -} - -template <> -TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = - keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION - if (!sleIssuance->isFlag(lsfMPTCanEscrow)) - return tecNO_PERMISSION; - - // If the issuer is not the same as the issuer of the mpt, return - // tecNO_PERMISSION - if (sleIssuance->getAccountID(sfIssuer) != issuer) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the account does not have the mpt, return tecOBJECT_NOT_FOUND - if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account))) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the account is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = - requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has requireAuth set, check if the destination is - // authorized - if (auto const ter = - requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the account, return tecLOCKED - if (isFrozen(ctx.view, account, mptIssue)) - return tecLOCKED; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - // If the mpt cannot be transferred, return tecNO_AUTH - if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); - ter != tesSUCCESS) - return ter; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.get(), - fhIGNORE_FREEZE, - ahIGNORE_AUTH, - ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::zero) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - return tesSUCCESS; -} - TER EscrowCreate::preclaim(PreclaimContext const& ctx) { @@ -384,8 +185,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) if (auto const ret = std::visit( [&](T const&) { - return escrowCreatePreclaimHelper( - ctx, account, dest, amount); + return createPreclaimHelper(ctx, account, dest, amount); }, amount.asset().value()); !isTesSuccess(ret)) @@ -394,63 +194,6 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -template -static TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal); - -template <> -TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal) -{ - // Defensive: Issuer cannot create an escrow - // LCOV_EXCL_START - if (issuer == sender) - return tecINTERNAL; - // LCOV_EXCL_STOP - - auto const ter = rippleCredit( - view, - sender, - issuer, - amount, - amount.holds() ? false : true, - journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - return tesSUCCESS; -} - -template <> -TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal) -{ - // Defensive: Issuer cannot create an escrow - // LCOV_EXCL_START - if (issuer == sender) - return tecINTERNAL; - // LCOV_EXCL_STOP - - auto const ter = rippleLockEscrowMPT(view, sender, amount, journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - return tesSUCCESS; -} - TER EscrowCreate::doApply() { @@ -692,71 +435,6 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) return Transactor::calculateBaseFee(view, tx) + extraFee; } -template -static TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount); - -template <> -TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tesSUCCESS - if (issuer == dest) - return tesSUCCESS; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); - ter != tesSUCCESS) - return ter; - - // If the issuer has deep frozen the destination, return tecFROZEN - if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer())) - return tecFROZEN; - - return tesSUCCESS; -} - -template <> -TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the dest, return tesSUCCESS - if (issuer == dest) - return tesSUCCESS; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = - keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the destination is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = - requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - return tesSUCCESS; -} - TER EscrowFinish::preclaim(PreclaimContext const& ctx) { @@ -782,7 +460,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) { if (auto const ret = std::visit( [&](T const&) { - return escrowFinishPreclaimHelper(ctx, dest, amount); + return escrowUnlockPreclaimHelper( + ctx.view, dest, amount); }, amount.asset().value()); !isTesSuccess(ret)) @@ -792,225 +471,6 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -template -static TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal); - -template <> -TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal) -{ - Keylet const trustLineKey = keylet::line(receiver, amount.issue()); - bool const recvLow = issuer > receiver; - bool const senderIssuer = issuer == sender; - bool const receiverIssuer = issuer == receiver; - bool const issuerHigh = issuer > receiver; - - // LCOV_EXCL_START - if (senderIssuer) - return tecINTERNAL; - // LCOV_EXCL_STOP - - if (receiverIssuer) - return tesSUCCESS; - - if (!view.exists(trustLineKey) && createAsset && !receiverIssuer) - { - // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - JLOG(journal.trace()) << "Trust line does not exist. " - "Insufficent reserve to create line."; - - return tecNO_LINE_INSUF_RESERVE; - } - - Currency const currency = amount.getCurrency(); - STAmount initialBalance(amount.issue()); - initialBalance.setIssuer(noAccount()); - - // clang-format off - if (TER const ter = trustCreate( - view, // payment sandbox - recvLow, // is dest low? - issuer, // source - receiver, // destination - trustLineKey.key, // ledger index - sleDest, // Account to add to - false, // authorize account - (sleDest->getFlags() & lsfDefaultRipple) == 0, - false, // freeze trust line - false, // deep freeze trust line - initialBalance, // zero initial balance - Issue(currency, receiver), // limit of zero - 0, // quality in - 0, // quality out - journal); // journal - !isTesSuccess(ter)) - { - return ter; // LCOV_EXCL_LINE - } - // clang-format on - - view.update(sleDest); - } - - if (!view.exists(trustLineKey) && !receiverIssuer) - return tecNO_LINE; - - auto const xferRate = transferRate(view, amount); - // update if issuer rate is less than locked rate - if (xferRate < lockedRate) - lockedRate = xferRate; - - // Transfer Rate only applies when: - // 1. Issuer is not involved in the transfer (senderIssuer or - // receiverIssuer) - // 2. The locked rate is different from the parity rate - - // NOTE: Transfer fee in escrow works a bit differently from a normal - // payment. In escrow, the fee is deducted from the locked/sending amount, - // whereas in a normal payment, the transfer fee is taken on top of the - // sending amount. - auto finalAmt = amount; - if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) - { - // compute transfer fee, if any - auto const xferFee = amount.value() - - divideRound(amount, lockedRate, amount.issue(), true); - // compute balance to transfer - finalAmt = amount.value() - xferFee; - } - - // validate the line limit if the account submitting txn is not the receiver - // of the funds - if (!createAsset) - { - auto const sleRippleState = view.peek(trustLineKey); - if (!sleRippleState) - return tecINTERNAL; // LCOV_EXCL_LINE - - // if the issuer is the high, then we use the low limit - // otherwise we use the high limit - STAmount const lineLimit = sleRippleState->getFieldAmount( - issuerHigh ? sfLowLimit : sfHighLimit); - - STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance); - - // flip the sign of the line balance if the issuer is not high - if (!issuerHigh) - lineBalance.negate(); - - // add the final amount to the line balance - lineBalance += finalAmt; - - // if the transfer would exceed the line limit return tecLIMIT_EXCEEDED - if (lineLimit < lineBalance) - return tecLIMIT_EXCEEDED; - } - - // if destination is not the issuer then transfer funds - if (!receiverIssuer) - { - auto const ter = - rippleCredit(view, issuer, receiver, finalAmt, true, journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - } - return tesSUCCESS; -} - -template <> -TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal) -{ - bool const senderIssuer = issuer == sender; - bool const receiverIssuer = issuer == receiver; - - auto const mptID = amount.get().getMptID(); - auto const issuanceKey = keylet::mptIssuance(mptID); - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && - createAsset && !receiverIssuer) - { - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - return tecINSUFFICIENT_RESERVE; - } - - if (auto const ter = - MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); - !isTesSuccess(ter)) - { - return ter; // LCOV_EXCL_LINE - } - - // update owner count. - adjustOwnerCount(view, sleDest, 1, journal); - } - - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && - !receiverIssuer) - return tecNO_PERMISSION; - - auto const xferRate = transferRate(view, amount); - // update if issuer rate is less than locked rate - if (xferRate < lockedRate) - lockedRate = xferRate; - - // Transfer Rate only applies when: - // 1. Issuer is not involved in the transfer (senderIssuer or - // receiverIssuer) - // 2. The locked rate is different from the parity rate - - // NOTE: Transfer fee in escrow works a bit differently from a normal - // payment. In escrow, the fee is deducted from the locked/sending amount, - // whereas in a normal payment, the transfer fee is taken on top of the - // sending amount. - auto finalAmt = amount; - if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) - { - // compute transfer fee, if any - auto const xferFee = amount.value() - - divideRound(amount, lockedRate, amount.asset(), true); - // compute balance to transfer - finalAmt = amount.value() - xferFee; - } - - return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal); -} - TER EscrowFinish::doApply() { @@ -1209,62 +669,62 @@ EscrowCancel::preflight(PreflightContext const& ctx) return preflight2(ctx); } -template -static TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount); - -template <> -TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecINTERNAL - if (issuer == account) - return tecINTERNAL; // LCOV_EXCL_LINE - - // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), account); - ter != tesSUCCESS) - return ter; - - return tesSUCCESS; -} - -template <> -TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecINTERNAL - if (issuer == account) - return tecINTERNAL; // LCOV_EXCL_LINE - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = - keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the account is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = - requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - return tesSUCCESS; -} +// template +// static TER +// escrowCancelPreclaimHelper( +// PreclaimContext const& ctx, +// AccountID const& account, +// STAmount const& amount); + +// template <> +// TER +// escrowCancelPreclaimHelper( +// PreclaimContext const& ctx, +// AccountID const& account, +// STAmount const& amount) +// { +// AccountID issuer = amount.getIssuer(); +// // If the issuer is the same as the account, return tecINTERNAL +// if (issuer == account) +// return tecINTERNAL; // LCOV_EXCL_LINE + +// // If the issuer has requireAuth set, check if the account is authorized +// if (auto const ter = requireAuth(ctx.view, amount.issue(), account); +// ter != tesSUCCESS) +// return ter; + +// return tesSUCCESS; +// } + +// template <> +// TER +// escrowCancelPreclaimHelper( +// PreclaimContext const& ctx, +// AccountID const& account, +// STAmount const& amount) +// { +// AccountID issuer = amount.getIssuer(); +// // If the issuer is the same as the account, return tecINTERNAL +// if (issuer == account) +// return tecINTERNAL; // LCOV_EXCL_LINE + +// // If the mpt does not exist, return tecOBJECT_NOT_FOUND +// auto const issuanceKey = +// keylet::mptIssuance(amount.get().getMptID()); +// auto const sleIssuance = ctx.view.read(issuanceKey); +// if (!sleIssuance) +// return tecOBJECT_NOT_FOUND; + +// // If the issuer has requireAuth set, check if the account is +// // authorized +// auto const& mptIssue = amount.get(); +// if (auto const ter = +// requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); +// ter != tesSUCCESS) +// return ter; + +// return tesSUCCESS; +// } TER EscrowCancel::preclaim(PreclaimContext const& ctx) @@ -1283,8 +743,8 @@ EscrowCancel::preclaim(PreclaimContext const& ctx) { if (auto const ret = std::visit( [&](T const&) { - return escrowCancelPreclaimHelper( - ctx, account, amount); + return escrowUnlockPreclaimHelper( + ctx.view, account, amount, false); }, amount.asset().value()); !isTesSuccess(ret)) @@ -1407,4 +867,4 @@ EscrowCancel::doApply() return tesSUCCESS; } -} // namespace ripple +} // namespace ripple \ No newline at end of file diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index d93378d3cde..dc3d2bbb881 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -105,8 +105,10 @@ XRPNotCreated::visitEntry( drops_ -= (*before)[sfBalance].xrp().drops(); break; case ltPAYCHAN: - drops_ -= - ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); + if (isXRP((*after)[sfAmount])) + drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]) + .xrp() + .drops(); break; case ltESCROW: if (isXRP((*before)[sfAmount])) @@ -125,7 +127,7 @@ XRPNotCreated::visitEntry( drops_ += (*after)[sfBalance].xrp().drops(); break; case ltPAYCHAN: - if (!isDelete) + if (!isDelete && isXRP((*after)[sfAmount])) drops_ += ((*after)[sfAmount] - (*after)[sfBalance]) .xrp() .drops(); @@ -1510,6 +1512,9 @@ ValidMPTIssuance::finalize( if (tx.getTxnType() == ttESCROW_FINISH) return true; + + if (tx.getTxnType() == ttPAYCHAN_CLAIM) + return true; } if (mptIssuancesCreated_ != 0) diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index d9e53ac75c7..b53bdfde563 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -118,6 +119,7 @@ closeChannel( std::shared_ptr const& slep, ApplyView& view, uint256 const& key, + AccountID const& txAccount, beast::Journal j) { AccountID const src = (*slep)[sfAccount]; @@ -133,10 +135,10 @@ closeChannel( } // Remove PayChan from recipient's owner directory, if present. + AccountID const dst = (*slep)[sfDestination]; if (auto const page = (*slep)[~sfDestinationNode]; page && view.rules().enabled(fixPayChanRecipientOwnerDir)) { - auto const dst = (*slep)[sfDestination]; if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true)) { JLOG(j.fatal()) @@ -153,8 +155,64 @@ closeChannel( XRPL_ASSERT( (*slep)[sfAmount] >= (*slep)[sfBalance], "ripple::closeChannel : minimum channel amount"); - (*sle)[sfBalance] = - (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance]; + + auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance]; + auto const issuer = reqDelta.getIssuer(); + + // Only Update the balance if there is a positive delta. + if (reqDelta > beast::zero) + { + if (isXRP(reqDelta)) + (*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta; + else + { + if (!view.rules().enabled(featureTokenPaychan)) + return temDISABLED; + + if (auto const ret = std::visit( + [&](T const&) { + return escrowUnlockPreclaimHelper( + view, src, reqDelta, false); + }, + reqDelta.asset().value()); + !isTesSuccess(ret)) + return ret; + + // Rate lockedRate = slep->isFieldPresent(sfTransferRate) + // ? ripple::Rate(slep->getFieldU32(sfTransferRate)) + // : parityRate; + bool const createAsset = src == txAccount; + if (auto const ret = std::visit( + [&](T const&) { + return escrowUnlockApplyHelper( + view, + parityRate, + sle, + (*sle)[sfBalance], + reqDelta, + issuer, + src, + src, + createAsset, + j); + }, + reqDelta.asset().value()); + !isTesSuccess(ret)) + return ret; + } + } + + // Remove escrow from issuers owner directory, if present. + if (auto const optPage = (*slep)[~sfIssuerNode]; optPage) + { + if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true)) + { + JLOG(j.fatal()) + << "Could not remove paychan from issuer owner directory"; + return tefBAD_LEDGER; + } + } + adjustOwnerCount(view, sle, -1, j); view.update(sle); @@ -168,7 +226,8 @@ closeChannel( TxConsequences PayChanCreate::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; + return TxConsequences{ + ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero}; } NotTEC @@ -180,9 +239,24 @@ PayChanCreate::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero)) + if (isXRP(ctx.tx[sfAmount]) && (ctx.tx[sfAmount] <= beast::zero)) return temBAD_AMOUNT; + STAmount const amount{ctx.tx[sfAmount]}; + if (!isXRP(amount)) + { + if (!ctx.rules.enabled(featureTokenPaychan)) + return temBAD_AMOUNT; + + if (auto const ret = std::visit( + [&](T const&) { + return createPreflightHelper(ctx); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } + if (ctx.tx[sfAccount] == ctx.tx[sfDestination]) return temDST_IS_SRC; @@ -200,6 +274,7 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) if (!sle) return terNO_ACCOUNT; + STAmount const amount{ctx.tx[sfAmount]}; // Check reserve and funds availability { auto const balance = (*sle)[sfBalance]; @@ -209,15 +284,15 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) if (balance < reserve) return tecINSUFFICIENT_RESERVE; - if (balance < reserve + ctx.tx[sfAmount]) + if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount]) return tecUNFUNDED; } - auto const dst = ctx.tx[sfDestination]; + auto const dest = ctx.tx[sfDestination]; { // Check destination account - auto const sled = ctx.view.read(keylet::account(dst)); + auto const sled = ctx.view.read(keylet::account(dest)); if (!sled) return tecNO_DST; @@ -247,6 +322,17 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; } + if (!isXRP(amount)) + { + if (auto const ret = std::visit( + [&](T const&) { + return createPreclaimHelper(ctx, account, dest, amount); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } + return tesSUCCESS; } @@ -254,6 +340,7 @@ TER PayChanCreate::doApply() { auto const account = ctx_.tx[sfAccount]; + STAmount const amount{ctx_.tx[sfAmount]}; auto const sle = ctx_.view().peek(keylet::account(account)); if (!sle) return tefINTERNAL; @@ -265,28 +352,35 @@ PayChanCreate::doApply() return tecEXPIRED; } - auto const dst = ctx_.tx[sfDestination]; + auto const dest = ctx_.tx[sfDestination]; // Create PayChan in ledger. // // Note that we we use the value from the sequence or ticket as the // payChan sequence. For more explanation see comments in SeqProxy.h. Keylet const payChanKeylet = - keylet::payChan(account, dst, ctx_.tx.getSeqValue()); + keylet::payChan(account, dest, ctx_.tx.getSeqValue()); auto const slep = std::make_shared(payChanKeylet); // Funds held in this channel - (*slep)[sfAmount] = ctx_.tx[sfAmount]; + (*slep)[sfAmount] = amount; // Amount channel has already paid - (*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed(); + (*slep)[sfBalance] = amount.zeroed(); (*slep)[sfAccount] = account; - (*slep)[sfDestination] = dst; + (*slep)[sfDestination] = dest; (*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay]; (*slep)[sfPublicKey] = ctx_.tx[sfPublicKey]; (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; + if (ctx_.view().rules().enabled(featureTokenPaychan) && !isXRP(amount)) + { + auto const xferRate = transferRate(ctx_.view(), amount); + if (xferRate != parityRate) + (*slep)[sfTransferRate] = xferRate.value; + } + ctx_.view().insert(slep); // Add PayChan to owner directory @@ -304,14 +398,38 @@ PayChanCreate::doApply() if (ctx_.view().rules().enabled(fixPayChanRecipientOwnerDir)) { auto const page = ctx_.view().dirInsert( - keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst)); + keylet::ownerDir(dest), payChanKeylet, describeOwnerDir(dest)); if (!page) return tecDIR_FULL; (*slep)[sfDestinationNode] = *page; } + AccountID const issuer = amount.getIssuer(); + if (!isXRP(amount) && issuer != account_ && issuer != dest && + !amount.holds()) + { + auto page = ctx_.view().dirInsert( + keylet::ownerDir(issuer), payChanKeylet, describeOwnerDir(issuer)); + if (!page) + return tecDIR_FULL; + (*slep)[sfIssuerNode] = *page; + } + // Deduct owner's balance, increment owner count - (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; + if (isXRP(amount)) + (*sle)[sfBalance] = (*sle)[sfBalance] - amount; + else + { + if (auto const ret = std::visit( + [&](T const&) { + return escrowLockApplyHelper( + ctx_.view(), issuer, account_, amount, j_); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } + adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); ctx_.view().update(sle); @@ -323,7 +441,8 @@ PayChanCreate::doApply() TxConsequences PayChanFund::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; + return TxConsequences{ + ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero}; } NotTEC @@ -335,9 +454,24 @@ PayChanFund::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero)) + STAmount const amount{ctx.tx[sfAmount]}; + if (isXRP(amount) && amount <= beast::zero) return temBAD_AMOUNT; + if (!isXRP(amount)) + { + if (!ctx.rules.enabled(featureTokenPaychan)) + return temBAD_AMOUNT; + + if (auto const ret = std::visit( + [&](T const&) { + return createPreflightHelper(ctx); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } + return preflight2(ctx); } @@ -350,7 +484,7 @@ PayChanFund::doApply() return tecNO_ENTRY; AccountID const src = (*slep)[sfAccount]; - auto const txAccount = ctx_.tx[sfAccount]; + AccountID const dst = (*slep)[sfDestination]; auto const expiration = (*slep)[~sfExpiration]; { @@ -360,10 +494,10 @@ PayChanFund::doApply() if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration)) return closeChannel( - slep, ctx_.view(), k.key, ctx_.app.journal("View")); + slep, ctx_.view(), k.key, account_, ctx_.app.journal("View")); } - if (src != txAccount) + if (src != account_) // only the owner can add funds or extend return tecNO_PERMISSION; @@ -381,10 +515,11 @@ PayChanFund::doApply() ctx_.view().update(slep); } - auto const sle = ctx_.view().peek(keylet::account(txAccount)); + auto const sle = ctx_.view().peek(keylet::account(account_)); if (!sle) return tefINTERNAL; + STAmount const amount{ctx_.tx[sfAmount]}; { // Check reserve and funds availability auto const balance = (*sle)[sfBalance]; @@ -394,21 +529,44 @@ PayChanFund::doApply() if (balance < reserve) return tecINSUFFICIENT_RESERVE; - if (balance < reserve + ctx_.tx[sfAmount]) + if (isXRP(amount) && balance < reserve + amount) return tecUNFUNDED; } + // if (!isXRP(amount)) + // { + // if (auto const ret = std::visit( + // [&](T const&) { + // return createPreclaimHelper(ctx_, src, dst, amount); + // }, + // amount.asset().value()); + // !isTesSuccess(ret)) + // return ret; + // } + // do not allow adding funds if dst does not exist - if (AccountID const dst = (*slep)[sfDestination]; - !ctx_.view().read(keylet::account(dst))) + if (!ctx_.view().read(keylet::account(dst))) { return tecNO_DST; } - (*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount]; - ctx_.view().update(slep); + if (isXRP(amount)) + (*sle)[sfBalance] = (*sle)[sfBalance] - amount; + else + { + AccountID const issuer = amount.getIssuer(); + if (auto const ret = std::visit( + [&](T const&) { + return escrowLockApplyHelper( + ctx_.view(), issuer, account_, amount, j_); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } - (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; + (*slep)[sfAmount] = (*slep)[sfAmount] + amount; + ctx_.view().update(slep); ctx_.view().update(sle); return tesSUCCESS; @@ -427,16 +585,18 @@ PayChanClaim::preflight(PreflightContext const& ctx) return ret; auto const bal = ctx.tx[~sfBalance]; - if (bal && (!isXRP(*bal) || *bal <= beast::zero)) + if (bal && *bal <= beast::zero) return temBAD_AMOUNT; auto const amt = ctx.tx[~sfAmount]; - if (amt && (!isXRP(*amt) || *amt <= beast::zero)) + if (amt && *amt <= beast::zero) return temBAD_AMOUNT; if (bal && amt && *bal > *amt) return temBAD_AMOUNT; + // std::cout << "PayChanClaim::preflight: balance: " << *bal << std::endl; + { auto const flags = ctx.tx.getFlags(); @@ -456,8 +616,8 @@ PayChanClaim::preflight(PreflightContext const& ctx) // The signature isn't needed if txAccount == src, but if it's // present, check it - auto const reqBalance = bal->xrp(); - auto const authAmt = amt ? amt->xrp() : reqBalance; + auto const reqBalance = bal; + auto const authAmt = amt ? amt : reqBalance; if (reqBalance > authAmt) return temBAD_AMOUNT; @@ -468,7 +628,7 @@ PayChanClaim::preflight(PreflightContext const& ctx) PublicKey const pk(ctx.tx[sfPublicKey]); Serializer msg; - serializePayChanAuthorization(msg, k.key, authAmt); + serializePayChanAuthorization(msg, k.key, *authAmt); if (!verify(pk, msg.slice(), *sig, /*canonical*/ true)) return temBAD_SIGNATURE; } @@ -491,6 +651,27 @@ PayChanClaim::preclaim(PreclaimContext const& ctx) !isTesSuccess(err)) return err; + Keylet const k(ltPAYCHAN, ctx.tx[sfChannel]); + auto const slep = ctx.view.read(k); + if (!slep) + return tecNO_TARGET; + + AccountID const dest = (*slep)[sfDestination]; + STAmount const amount = (*slep)[sfAmount]; + if (!isXRP(amount) && ctx.tx.isFieldPresent(sfBalance)) + { + if (!ctx.view.rules().enabled(featureTokenPaychan)) + return temDISABLED; + + if (auto const ret = std::visit( + [&](T const&) { + return escrowUnlockPreclaimHelper( + ctx.view, dest, amount); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } return tesSUCCESS; } @@ -504,7 +685,6 @@ PayChanClaim::doApply() AccountID const src = (*slep)[sfAccount]; AccountID const dst = (*slep)[sfDestination]; - AccountID const txAccount = ctx_.tx[sfAccount]; auto const curExpiration = (*slep)[~sfExpiration]; { @@ -514,19 +694,19 @@ PayChanClaim::doApply() if ((cancelAfter && closeTime >= *cancelAfter) || (curExpiration && closeTime >= *curExpiration)) return closeChannel( - slep, ctx_.view(), k.key, ctx_.app.journal("View")); + slep, ctx_.view(), k.key, account_, ctx_.app.journal("View")); } - if (txAccount != src && txAccount != dst) + if (account_ != src && account_ != dst) return tecNO_PERMISSION; if (ctx_.tx[~sfBalance]) { - auto const chanBalance = slep->getFieldAmount(sfBalance).xrp(); - auto const chanFunds = slep->getFieldAmount(sfAmount).xrp(); - auto const reqBalance = ctx_.tx[sfBalance].xrp(); + auto const chanBalance = slep->getFieldAmount(sfBalance); + auto const chanFunds = slep->getFieldAmount(sfAmount); + auto const reqBalance = ctx_.tx[sfBalance]; - if (txAccount == dst && !ctx_.tx[~sfSignature]) + if (account_ == dst && !ctx_.tx[~sfSignature]) return temBAD_SIGNATURE; if (ctx_.tx[~sfSignature]) @@ -551,30 +731,62 @@ PayChanClaim::doApply() // featureDepositAuth to remove the bug. bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)}; if (!depositAuth && - (txAccount == src && (sled->getFlags() & lsfDisallowXRP))) + (account_ == src && (sled->getFlags() & lsfDisallowXRP))) return tecNO_TARGET; if (depositAuth) { if (auto err = verifyDepositPreauth( - ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal); + ctx_.tx, ctx_.view(), account_, dst, sled, ctx_.journal); !isTesSuccess(err)) return err; } (*slep)[sfBalance] = ctx_.tx[sfBalance]; - XRPAmount const reqDelta = reqBalance - chanBalance; + STAmount const reqDelta = reqBalance - chanBalance; XRPL_ASSERT( reqDelta >= beast::zero, "ripple::PayChanClaim::doApply : minimum balance delta"); - (*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta; + + // Transfer amount to destination + if (isXRP(reqDelta)) + (*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta; + else + { + if (!ctx_.view().rules().enabled(featureTokenPaychan)) + return temDISABLED; + + Rate lockedRate = slep->isFieldPresent(sfTransferRate) + ? ripple::Rate(slep->getFieldU32(sfTransferRate)) + : parityRate; + auto const issuer = reqDelta.getIssuer(); + bool const createAsset = dst == account_; + if (auto const ret = std::visit( + [&](T const&) { + return escrowUnlockApplyHelper( + ctx_.view(), + lockedRate, + sled, + mPriorBalance, + reqDelta, + issuer, + src, + dst, + createAsset, + j_); + }, + reqDelta.asset().value()); + !isTesSuccess(ret)) + return ret; + } + ctx_.view().update(sled); ctx_.view().update(slep); } if (ctx_.tx.getFlags() & tfRenew) { - if (src != txAccount) + if (src != account_) return tecNO_PERMISSION; (*slep)[~sfExpiration] = std::nullopt; ctx_.view().update(slep); @@ -583,9 +795,9 @@ PayChanClaim::doApply() if (ctx_.tx.getFlags() & tfClose) { // Channel will close immediately if dry or the receiver closes - if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount]) + if (dst == account_ || (*slep)[sfBalance] == (*slep)[sfAmount]) return closeChannel( - slep, ctx_.view(), k.key, ctx_.app.journal("View")); + slep, ctx_.view(), k.key, account_, ctx_.app.journal("View")); auto const settleExpiration = ctx_.view().info().parentCloseTime.time_since_epoch().count() + diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp index ca9e370c815..255ca663885 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -150,18 +150,49 @@ doGatewayBalances(RPC::JsonContext& context) *ledger, accountID, [&](std::shared_ptr const& sle) { if (sle->getType() == ltESCROW) { - auto const& escrow = sle->getFieldAmount(sfAmount); - auto& bal = locked[escrow.getCurrency()]; + auto const& amount = sle->getFieldAmount(sfAmount); + auto& bal = locked[amount.getCurrency()]; if (bal == beast::zero) { // This is needed to set the currency code correctly - bal = escrow; + bal = amount; } else { try { - bal += escrow; + bal += amount; + } + catch (std::runtime_error const&) + { + // Presumably the exception was caused by overflow. + // On overflow return the largest valid STAmount. + // Very large sums of STAmount are approximations + // anyway. + bal = STAmount( + bal.issue(), + STAmount::cMaxValue, + STAmount::cMaxOffset); + } + } + } + + if (sle->getType() == ltPAYCHAN) + { + auto const& amount = sle->getFieldAmount(sfAmount); + auto const& balance = sle->getFieldAmount(sfBalance); + auto const& netAmount = amount - balance; + auto& bal = locked[netAmount.getCurrency()]; + if (bal == beast::zero) + { + // This is needed to set the currency code correctly + bal = netAmount; + } + else + { + try + { + bal += netAmount; } catch (std::runtime_error const&) { From 69add359252fa4a3af8f98bed2146fbd64701029 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 6 May 2026 20:55:03 +0200 Subject: [PATCH 2/3] bad merge --- include/xrpl/ledger/helpers/EscrowHelpers.h | 196 +---- include/xrpl/protocol/PayChan.h | 31 +- src/libxrpl/tx/invariants/InvariantCheck.cpp | 6 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 6 +- .../tx/transactors/escrow/EscrowCreate.cpp | 163 +++- src/test/app/AMM_test.cpp | 7 +- src/test/app/MPToken_test.cpp | 38 +- src/test/app/PayChanToken_test.cpp | 798 +++++++----------- src/test/app/PayChan_test.cpp | 84 +- src/test/jtx/TestHelpers.h | 65 +- src/test/jtx/impl/TestHelpers.cpp | 105 +-- src/test/jtx/impl/paychan.cpp | 37 +- src/test/jtx/paychan.h | 26 +- src/xrpld/app/misc/EscrowUtils.h | 580 ------------- .../rpc/handlers/account/GatewayBalances.cpp | 6 +- 15 files changed, 552 insertions(+), 1596 deletions(-) delete mode 100644 src/xrpld/app/misc/EscrowUtils.h diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 0b64780bf93..fc2981b7399 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -13,202 +13,9 @@ #include #include #include -#include namespace xrpl { -//------------------------------------------------------------------------------ -// Shared preflight/preclaim/apply helpers for Escrow and PaymentChannel -//------------------------------------------------------------------------------ - -template -NotTEC -createPreflightHelper(PreflightContext const& ctx); - -template <> -inline NotTEC -createPreflightHelper(PreflightContext const& ctx) -{ - STAmount const amount = ctx.tx[sfAmount]; - if (amount.native() || amount <= beast::kZERO) - return temBAD_AMOUNT; - - if (badCurrency() == amount.get().currency) - return temBAD_CURRENCY; - - return tesSUCCESS; -} - -template <> -inline NotTEC -createPreflightHelper(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureMPTokensV1)) - return temDISABLED; - - auto const amount = ctx.tx[sfAmount]; - if (amount.native() || amount.mpt() > MPTAmount{kMAX_MP_TOKEN_AMOUNT} || amount <= beast::kZERO) - return temBAD_AMOUNT; - - return tesSUCCESS; -} - -template -TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount); - -template <> -inline TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - Issue const& issue = amount.get(); - AccountID const& issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION - auto const sleIssuer = ctx.view.read(keylet::account(issuer)); - if (!sleIssuer) - return tecNO_ISSUER; - if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) - return tecNO_PERMISSION; - - // If the account does not have a trustline to the issuer, return tecNO_LINE - auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, issue.currency)); - if (!sleRippleState) - return tecNO_LINE; - - STAmount const balance = (*sleRippleState)[sfBalance]; - - // If balance is positive, issuer must have higher address than account - if (balance > beast::kZERO && issuer < account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If balance is negative, issuer must have lower address than account - if (balance < beast::kZERO && issuer > account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter)) - return ter; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter)) - return ter; - - // If the issuer has frozen the account, return tecFROZEN - if (isFrozen(ctx.view, account, issue)) - return tecFROZEN; - - // If the issuer has frozen the destination, return tecFROZEN - if (isFrozen(ctx.view, dest, issue)) - return tecFROZEN; - - STAmount const spendableAmount = accountHolds( - ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::kZERO) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - // If the amount is not addable to the balance, return tecPRECISION_LOSS - if (!canAdd(spendableAmount, amount)) - return tecPRECISION_LOSS; - - return tesSUCCESS; -} - -template <> -inline TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - AccountID const issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION - if (!sleIssuance->isFlag(lsfMPTCanEscrow)) - return tecNO_PERMISSION; - - // If the issuer is not the same as the issuer of the mpt, return - // tecNO_PERMISSION - if (sleIssuance->getAccountID(sfIssuer) != issuer) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the account does not have the mpt, return tecOBJECT_NOT_FOUND - if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account))) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the account is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); - !isTesSuccess(ter)) - return ter; - - // If the issuer has requireAuth set, check if the destination is - // authorized - if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); - !isTesSuccess(ter)) - return ter; - - // If the issuer has frozen the account, return tecLOCKED - if (isFrozen(ctx.view, account, mptIssue)) - return tecLOCKED; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - // If the mpt cannot be transferred, return tecNO_AUTH - if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter)) - return ter; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.get(), - FreezeHandling::IgnoreFreeze, - AuthHandling::IgnoreAuth, - ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::kZERO) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - return tesSUCCESS; -} - template TER escrowLockApplyHelper( @@ -283,7 +90,8 @@ escrowUnlockPreclaimHelper( return ter; // If the issuer has deep frozen the destination, return tecFROZEN - if (checkFreeze && isDeepFrozen(view, account, amount.get().currency, amount.getIssuer())) + if (checkFreeze && + isDeepFrozen(view, account, amount.get().currency, amount.getIssuer())) return tecFROZEN; return tesSUCCESS; diff --git a/include/xrpl/protocol/PayChan.h b/include/xrpl/protocol/PayChan.h index 2d35d2067bc..9dd2586b70b 100644 --- a/include/xrpl/protocol/PayChan.h +++ b/include/xrpl/protocol/PayChan.h @@ -1,8 +1,14 @@ #pragma once #include +#include #include +#include +#include +#include +#include #include +#include #include namespace xrpl { @@ -23,20 +29,17 @@ serializePayChanAuthorization( Currency const& cur, AccountID const& iss) { - msg.add32(HashPrefix::paymentChannelClaim); + msg.add32(HashPrefix::PaymentChannelClaim); msg.addBitString(key); - if (amt == beast::zero) - msg.add64(STAmount::cIssuedCurrency); + if (amt == beast::kZERO) + msg.add64(STAmount::kISSUED_CURRENCY); else if (amt.signum() == -1) // 512 = not native msg.add64( - amt.mantissa() | - (static_cast(amt.exponent() + 512 + 97) - << (64 - 10))); + amt.mantissa() | (static_cast(amt.exponent() + 512 + 97) << (64 - 10))); else // 256 = positive msg.add64( amt.mantissa() | - (static_cast(amt.exponent() + 512 + 256 + 97) - << (64 - 10))); + (static_cast(amt.exponent() + 512 + 256 + 97) << (64 - 10))); msg.addBitString(cur); msg.addBitString(iss); } @@ -49,7 +52,7 @@ serializePayChanAuthorization( MPTID const& mptID, AccountID const& iss) { - msg.add32(HashPrefix::paymentChannelClaim); + msg.add32(HashPrefix::PaymentChannelClaim); msg.addBitString(key); msg.add64(amt.value()); msg.addBitString(mptID); @@ -57,22 +60,18 @@ serializePayChanAuthorization( } inline void -serializePayChanAuthorization( - Serializer& msg, - uint256 const& key, - STAmount const& amt) +serializePayChanAuthorization(Serializer& msg, uint256 const& key, STAmount const& amt) { if (amt.native()) serializePayChanAuthorization(msg, key, amt.xrp()); else if (amt.holds()) serializePayChanAuthorization( - msg, key, amt.iou(), amt.issue().currency, amt.issue().account); + msg, key, amt.iou(), amt.get().currency, amt.get().account); else if (amt.holds()) { auto const mpt = amt.get(); auto const mptID = mpt.getMptID(); - serializePayChanAuthorization( - msg, key, amt.mpt(), mptID, amt.getIssuer()); + serializePayChanAuthorization(msg, key, amt.mpt(), mptID, amt.getIssuer()); } } diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 206c6921988..0e04a5667cb 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -130,10 +130,8 @@ XRPNotCreated::visitEntry( drops_ -= (*before)[sfBalance].xrp().drops(); break; case ltPAYCHAN: - if (isXRP((*after)[sfAmount])) - drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]) - .xrp() - .drops(); + if (isXRP((*before)[sfAmount])) + drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); break; case ltESCROW: if (isXRP((*before)[sfAmount])) diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 3cac6f29a1a..838254ab649 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -253,7 +253,11 @@ ValidMPTIssuance::finalize( } if (tx.getTxnType() == ttPAYCHAN_CLAIM) - return true; + { + if (mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && + mptokensDeleted_ == 0 && mptokensCreated_ <= 1) + return true; + } if (hasPrivilege(tx, MayDeleteMpt) && ((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) && diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index eb3febf540c..bcc32cf4293 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -82,6 +82,38 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::kZERO}; } +template +static NotTEC +escrowCreatePreflightHelper(PreflightContext const& ctx); + +template <> +NotTEC +escrowCreatePreflightHelper(PreflightContext const& ctx) +{ + STAmount const amount = ctx.tx[sfAmount]; + if (amount.native() || amount <= beast::kZERO) + return temBAD_AMOUNT; + + if (badCurrency() == amount.get().currency) + return temBAD_CURRENCY; + + return tesSUCCESS; +} + +template <> +NotTEC +escrowCreatePreflightHelper(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + auto const amount = ctx.tx[sfAmount]; + if (amount.native() || amount.mpt() > MPTAmount{kMAX_MP_TOKEN_AMOUNT} || amount <= beast::kZERO) + return temBAD_AMOUNT; + + return tesSUCCESS; +} + NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -92,7 +124,7 @@ EscrowCreate::preflight(PreflightContext const& ctx) return temBAD_AMOUNT; if (auto const ret = std::visit( - [&](T const&) { return createPreflightHelper(ctx); }, + [&](T const&) { return escrowCreatePreflightHelper(ctx); }, amount.asset().value()); !isTesSuccess(ret)) return ret; @@ -137,6 +169,133 @@ EscrowCreate::preflight(PreflightContext const& ctx) return tesSUCCESS; } +template +static TER +escrowCreatePreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount); + +template <> +TER +escrowCreatePreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount) +{ + Issue const& issue = amount.get(); + AccountID const& issuer = amount.getIssuer(); + if (issuer == account) + return tecNO_PERMISSION; + + auto const sleIssuer = ctx.view.read(keylet::account(issuer)); + if (!sleIssuer) + return tecNO_ISSUER; + if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) + return tecNO_PERMISSION; + + auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, issue.currency)); + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + if (balance > beast::kZERO && issuer < account) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + if (balance < beast::kZERO && issuer > account) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter)) + return ter; + + if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter)) + return ter; + + if (isFrozen(ctx.view, account, issue)) + return tecFROZEN; + + if (isFrozen(ctx.view, dest, issue)) + return tecFROZEN; + + STAmount const spendableAmount = accountHolds( + ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.j); + + if (spendableAmount <= beast::kZERO) + return tecINSUFFICIENT_FUNDS; + + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + if (!canAdd(spendableAmount, amount)) + return tecPRECISION_LOSS; + + return tesSUCCESS; +} + +template <> +TER +escrowCreatePreclaimHelper( + PreclaimContext const& ctx, + AccountID const& account, + AccountID const& dest, + STAmount const& amount) +{ + AccountID const issuer = amount.getIssuer(); + if (issuer == account) + return tecNO_PERMISSION; + + auto const issuanceKey = keylet::mptIssuance(amount.get().getMptID()); + auto const sleIssuance = ctx.view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + if (!sleIssuance->isFlag(lsfMPTCanEscrow)) + return tecNO_PERMISSION; + + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account))) + return tecOBJECT_NOT_FOUND; + + auto const& mptIssue = amount.get(); + if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); + !isTesSuccess(ter)) + return ter; + + if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); + !isTesSuccess(ter)) + return ter; + + if (isFrozen(ctx.view, account, mptIssue)) + return tecLOCKED; + + if (isFrozen(ctx.view, dest, mptIssue)) + return tecLOCKED; + + if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter)) + return ter; + + STAmount const spendableAmount = accountHolds( + ctx.view, + account, + amount.get(), + FreezeHandling::IgnoreFreeze, + AuthHandling::IgnoreAuth, + ctx.j); + + if (spendableAmount <= beast::kZERO) + return tecINSUFFICIENT_FUNDS; + + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + TER EscrowCreate::preclaim(PreclaimContext const& ctx) { @@ -162,7 +321,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) if (auto const ret = std::visit( [&](T const&) { - return createPreclaimHelper(ctx, account, dest, amount); + return escrowCreatePreclaimHelper(ctx, account, dest, amount); }, amount.asset().value()); !isTesSuccess(ret)) diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 88fb4ea4b4c..8e2c0255ef6 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -3238,12 +3238,7 @@ struct AMM_test : public jtx::AMMTest auto const settleDelay = 100s; NetClock::time_point const cancelAfter = env.current()->header().parentCloseTime + 200s; env(paychan::create( - carol_, - ammAlice.ammAccount(), - XRP(1'000), - settleDelay, - pk, - cancelAfter), + carol_, ammAlice.ammAccount(), XRP(1'000), settleDelay, pk, cancelAfter), Ter(tecNO_PERMISSION)); }); diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 31f7f179097..62551be05ec 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -1960,14 +1960,14 @@ class MPToken_test : public beast::unit_test::Suite test(jv, field.fieldName); }; for (SField const& field : - {toSFieldRef(sfBidMin), - toSFieldRef(sfBidMax), - toSFieldRef(sfAsset), - toSFieldRef(sfAsset2)}) + {std::cref(static_cast(sfBidMin)), + std::cref(static_cast(sfBidMax)), + std::cref(static_cast(sfAsset)), + std::cref(static_cast(sfAsset2))}) ammBid(field); // AMMClawback auto ammClawback = [&](SField const& field) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::AMMClawback; jv[jss::Account] = alice.human(); jv[jss::Holder] = carol.human(); @@ -1975,13 +1975,13 @@ class MPToken_test : public beast::unit_test::Suite test(jv, field.fieldName); }; for (SField const& field : - {toSFieldRef(sfAmount), - toSFieldRef(sfAsset), - toSFieldRef(sfAsset2)}) + {std::cref(static_cast(sfAmount)), + std::cref(static_cast(sfAsset)), + std::cref(static_cast(sfAsset2))}) ammClawback(field); // AMMDelete auto ammDelete = [&](SField const& field) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::AMMDelete; jv[jss::Account] = alice.human(); setMPTFields(field, jv, false); @@ -1991,7 +1991,7 @@ class MPToken_test : public beast::unit_test::Suite ammDelete(sfAsset2); // AMMVote auto ammVote = [&](SField const& field) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::AMMVote; jv[jss::Account] = alice.human(); jv[jss::TradingFee] = 100; @@ -2002,23 +2002,23 @@ class MPToken_test : public beast::unit_test::Suite ammVote(sfAsset2); // CheckCash auto checkCash = [&](SField const& field) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::CheckCash; jv[jss::Account] = alice.human(); jv[sfCheckID.fieldName] = to_string(uint256{1}); - jv[field.fieldName] = mpt.getJson(JsonOptions::none); + jv[field.fieldName] = mpt.getJson(JsonOptions::KNone); test(jv, field.fieldName); }; checkCash(sfAmount); checkCash(sfDeliverMin); // CheckCreate { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::CheckCreate; jv[jss::Account] = alice.human(); jv[jss::Destination] = carol.human(); - jv[jss::SendMax] = mpt.getJson(JsonOptions::none); - test(jv, jss::SendMax.c_str()); + jv[jss::SendMax] = mpt.getJson(JsonOptions::KNone); + test(jv, jss::SendMax.cStr()); } // PaymentChannelCreate { @@ -2051,10 +2051,10 @@ class MPToken_test : public beast::unit_test::Suite } // OfferCreate { - Json::Value jv = offer(alice, USD(100), mpt); - test(jv, jss::TakerPays.c_str()); - jv = offer(alice, mpt, USD(100)); - test(jv, jss::TakerGets.c_str()); + json::Value jv = offer(alice, usd(100), mpt); + test(jv, jss::TakerPays.cStr()); + jv = offer(alice, mpt, usd(100)); + test(jv, jss::TakerGets.cStr()); } // NFTokenCreateOffer { diff --git a/src/test/app/PayChanToken_test.cpp b/src/test/app/PayChanToken_test.cpp index 018cd0c1cc8..32900c61127 100644 --- a/src/test/app/PayChanToken_test.cpp +++ b/src/test/app/PayChanToken_test.cpp @@ -19,7 +19,7 @@ #include -#include +#include #include #include @@ -29,9 +29,9 @@ #include #include -namespace ripple { +namespace xrpl { namespace test { -struct PayChanToken_test : public beast::unit_test::suite +struct PayChanToken_test : public beast::unit_test::Suite { void testIOUEnablement(FeatureBitset features) @@ -43,8 +43,7 @@ struct PayChanToken_test : public beast::unit_test::suite for (bool const withTokenPaychan : {false, true}) { - auto const amend = - withTokenPaychan ? features : features - featureTokenPaychan; + auto const amend = withTokenPaychan ? features : features - featureTokenPaychan; Env env{*this, amend}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -59,20 +58,17 @@ struct PayChanToken_test : public beast::unit_test::suite env(pay(gw, bob, USD(5'000))); env.close(); - auto const openResult = - withTokenPaychan ? ter(tesSUCCESS) : ter(temBAD_AMOUNT); - auto const closeResult = - withTokenPaychan ? ter(tesSUCCESS) : ter(tecNO_TARGET); + auto const openResult = withTokenPaychan ? Ter(tesSUCCESS) : Ter(temBAD_AMOUNT); + auto const closeResult = withTokenPaychan ? Ter(tesSUCCESS) : Ter(tecNO_TARGET); auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), - openResult); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), openResult); env.close(); env(paychan::fund(alice, chan, USD(1'000)), openResult); env.close(); - env(paychan::claim(bob, chan), txflags(tfClose), closeResult); + env(paychan::claim(bob, chan), Txflags(tfClose), closeResult); env.close(); } } @@ -103,30 +99,26 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), Ter(tesSUCCESS)); env.close(); // Clear the asfAllowTrustLineLocking flag env(fclear(gw, asfAllowTrustLineLocking)); env.close(); - env.require(nflags(gw, asfAllowTrustLineLocking)); + env.require(Nflags(gw, asfAllowTrustLineLocking)); // Cannot Create PayChan without asfAllowTrustLineLocking - env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), - ter(tecNO_PERMISSION)); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), Ter(tecNO_PERMISSION)); env.close(); // Can Fund PayChan without asfAllowTrustLineLocking - env(paychan::fund(alice, chan, USD(1'000)), ter(tesSUCCESS)); + env(paychan::fund(alice, chan, USD(1'000)), Ter(tesSUCCESS)); env.close(); // Can claim the paychan created before the flag was cleared - auto const sig = - paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1'000)); - env(paychan::claim( - bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1'000)); + env(paychan::claim(bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), + Ter(tesSUCCESS)); env.close(); } @@ -149,8 +141,8 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; env(paychan::create(alice, bob, USD(1), settleDelay, pk), - fee(XRP(-1)), - ter(temBAD_FEE)); + Fee(XRP(-1)), + Ter(temBAD_FEE)); env.close(); } @@ -165,8 +157,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, USD(-1), settleDelay, pk), - ter(temBAD_AMOUNT)); + env(paychan::create(alice, bob, USD(-1), settleDelay, pk), Ter(temBAD_AMOUNT)); env.close(); } @@ -181,8 +172,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, BAD(1), settleDelay, pk), - ter(temBAD_CURRENCY)); + env(paychan::create(alice, bob, BAD(1), settleDelay, pk), Ter(temBAD_CURRENCY)); env.close(); } } @@ -203,8 +193,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const USD = gw["USD"]; env.fund(XRP(5000), alice, bob, gw); - env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), - ter(tecNO_PERMISSION)); + env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), Ter(tecNO_PERMISSION)); env.close(); } @@ -219,8 +208,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); env.memoize(gw); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecNO_ISSUER)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecNO_ISSUER)); env.close(); } @@ -239,8 +227,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(pay(gw, bob, USD(5000))); env.close(); - env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), - ter(tecNO_PERMISSION)); + env(paychan::create(gw, alice, USD(1), 100s, alice.pk()), Ter(tecNO_PERMISSION)); env.close(); } @@ -255,8 +242,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(fset(gw, asfAllowTrustLineLocking)); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecNO_LINE)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecNO_LINE)); env.close(); } @@ -276,8 +262,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.trust(USD(10'000), alice, bob); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecNO_AUTH)); env.close(); } @@ -293,12 +278,11 @@ struct PayChanToken_test : public beast::unit_test::suite env(fset(gw, asfAllowTrustLineLocking)); env(fset(gw, asfRequireAuth)); env.close(); - env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); + env(trust(gw, aliceUSD(10'000)), Txflags(tfSetfAuth)); env.trust(USD(10'000), alice, bob); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecNO_AUTH)); env.close(); } @@ -324,8 +308,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(trust(gw, USD(10'000), alice, tfSetFreeze)); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecFROZEN)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecFROZEN)); env.close(); } @@ -351,8 +334,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(trust(gw, USD(10'000), bob, tfSetFreeze)); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecFROZEN)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecFROZEN)); env.close(); } @@ -371,8 +353,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(trust(bob, USD(100'000))); env.close(); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecINSUFFICIENT_FUNDS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecINSUFFICIENT_FUNDS)); env.close(); } @@ -395,7 +376,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); env(paychan::create(alice, bob, USD(10'001), 100s, alice.pk()), - ter(tecINSUFFICIENT_FUNDS)); + Ter(tecINSUFFICIENT_FUNDS)); env.close(); } @@ -417,8 +398,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // alice cannot create paychan for 1/10 iou - precision loss - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tecPRECISION_LOSS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tecPRECISION_LOSS)); env.close(); } } @@ -443,8 +423,8 @@ struct PayChanToken_test : public beast::unit_test::suite env(fset(gw, asfAllowTrustLineLocking)); env(fset(gw, asfRequireAuth)); env.close(); - env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); - env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth)); + env(trust(gw, aliceUSD(10'000)), Txflags(tfSetfAuth)); + env(trust(gw, bobUSD(10'000)), Txflags(tfSetfAuth)); env.trust(USD(10'000), alice, bob); env.close(); env(pay(gw, alice, USD(10'000))); @@ -452,12 +432,11 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); env(pay(bob, gw, USD(10'000))); - env(trust(gw, bobUSD(0)), txflags(tfSetfAuth)); + env(trust(gw, bobUSD(0)), Txflags(tfSetfAuth)); env(trust(bob, USD(0))); env.close(); @@ -465,11 +444,8 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // bob cannot claim because he is not authorized - auto const sig = - paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); - env(paychan::claim( - bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), - ter(tecNO_AUTH)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim(bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), Ter(tecNO_AUTH)); env.close(); } @@ -490,19 +466,15 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); // set freeze on bob trustline env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze)); // bob cannot claim because of deep freeze - auto const sig = - paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); - env(paychan::claim( - bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), - ter(tecFROZEN)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim(bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), Ter(tecFROZEN)); env.close(); } } @@ -534,16 +506,13 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); // bob cannot claim because insufficient reserve to create line - auto const sig = - paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); - env(paychan::claim( - bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), - ter(tecNO_LINE_INSUF_RESERVE)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1)); + env(paychan::claim(bob, chan, USD(1), USD(1), Slice(sig), alice.pk()), + Ter(tecNO_LINE_INSUF_RESERVE)); env.close(); } @@ -564,12 +533,11 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); // alice cannot claim because bob does not have a trustline - env(paychan::claim(alice, chan, USD(1), USD(1)), ter(tecNO_LINE)); + env(paychan::claim(alice, chan, USD(1), USD(1)), Ter(tecNO_LINE)); env.close(); } @@ -590,16 +558,14 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); env.trust(USD(1), bob); env.close(); // alice cannot claim because bobs limit is too low - env(paychan::claim(alice, chan, USD(5), USD(5)), - ter(tecLIMIT_EXCEEDED)); + env(paychan::claim(alice, chan, USD(5), USD(5)), Ter(tecLIMIT_EXCEEDED)); env.close(); } @@ -620,8 +586,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(5), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); env.trust(USD(1), bob); @@ -630,11 +595,8 @@ struct PayChanToken_test : public beast::unit_test::suite auto const bobPreLimit = env.limit(bob, USD); // bob can claim even if bobs limit is too low - auto const sig = - paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(5)); - env(paychan::claim( - bob, chan, USD(5), USD(5), Slice(sig), alice.pk()), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(5)); + env(paychan::claim(bob, chan, USD(5), USD(5), Slice(sig), alice.pk()), Ter(tesSUCCESS)); env.close(); // bobs limit is not changed @@ -671,24 +633,20 @@ struct PayChanToken_test : public beast::unit_test::suite { auto const preAliceUSD = env.balance(alice, USD); auto const preBobUSD = env.balance(bob, USD); - env(paychan::create(alice, bob, USD(1'000), 1s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1'000), 1s, alice.pk()), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000)); BEAST_EXPECT(env.balance(bob, USD) == preBobUSD); - BEAST_EXPECT( - issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); + BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000)); } { auto const preAliceUSD = env.balance(alice, USD); auto const preBobUSD = env.balance(bob, USD); - auto const sig = paychan::signClaimAuth( - alice.pk(), alice.sk(), chan, USD(1'000)); - env(paychan::claim( - bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(1'000)); + env(paychan::claim(bob, chan, USD(1'000), USD(1'000), Slice(sig), alice.pk()), + Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD); @@ -702,22 +660,20 @@ struct PayChanToken_test : public beast::unit_test::suite { auto const preAliceUSD = env.balance(alice, USD); auto const preBobUSD = env.balance(bob, USD); - env(paychan::create(alice, bob, USD(1'000), 100s, alice.pk()), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, USD(1'000), 100s, alice.pk()), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000)); BEAST_EXPECT(env.balance(bob, USD) == preBobUSD); - BEAST_EXPECT( - issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); + BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000)); BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000)); } { auto const preAliceUSD = env.balance(alice, USD); auto const preBobUSD = env.balance(bob, USD); env(paychan::claim(alice, chan2, USD(1'000), USD(1'000)), - txflags(tfClose), - ter(tesSUCCESS)); + Txflags(tfClose), + Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD); @@ -759,13 +715,11 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); BEAST_EXPECT( - (*env.meta())[sfTransactionResult] == - static_cast(tesSUCCESS)); + (*env.meta())[sfTransactionResult] == static_cast(tesSUCCESS)); env.close(); env(paychan::create(bob, carol, USD(1'000), settleDelay, pk2)); BEAST_EXPECT( - (*env.meta())[sfTransactionResult] == - static_cast(tesSUCCESS)); + (*env.meta())[sfTransactionResult] == static_cast(tesSUCCESS)); env.close(); auto const ab = env.le(keylet::payChan(alice.id(), bob.id(), aseq)); @@ -775,94 +729,72 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(bc); { - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) != aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) != aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) != bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); - BEAST_EXPECT( - std::find(cod.begin(), cod.end(), bc) != cod.end()); + BEAST_EXPECT(std::find(cod.begin(), cod.end(), bc) != cod.end()); - ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + xrpl::Dir iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), ab) != iod.end()); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), bc) != iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) != iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) != iod.end()); } auto const chan_ab = paychan::channel(alice, bob, aseq); - env(paychan::claim(alice, chan_ab, USD(1'000), USD(1'000)), - txflags(tfClose)); + env(paychan::claim(alice, chan_ab, USD(1'000), USD(1'000)), Txflags(tfClose)); { - BEAST_EXPECT( - !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); - BEAST_EXPECT( - env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + BEAST_EXPECT(!env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT(env.le(keylet::payChan(bob.id(), carol.id(), bseq))); - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); - ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + xrpl::Dir iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), ab) == iod.end()); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), bc) != iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) != iod.end()); } env.close(); auto const chan_bc = paychan::channel(bob, carol, bseq); - env(paychan::claim(bob, chan_bc, USD(1'000), USD(1'000)), - txflags(tfClose)); + env(paychan::claim(bob, chan_bc, USD(1'000), USD(1'000)), Txflags(tfClose)); { - BEAST_EXPECT( - !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); - BEAST_EXPECT( - !env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + BEAST_EXPECT(!env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT(!env.le(keylet::payChan(bob.id(), carol.id(), bseq))); - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) == bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); - ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + xrpl::Dir iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), ab) == iod.end()); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), bc) == iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) == iod.end()); } } @@ -885,50 +817,43 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, gw, USD(1'000), settleDelay, pk)); BEAST_EXPECT( - (*env.meta())[sfTransactionResult] == - static_cast(tesSUCCESS)); + (*env.meta())[sfTransactionResult] == static_cast(tesSUCCESS)); env.close(); env(paychan::create(gw, carol, USD(1'000), settleDelay, alice.pk()), - ter(tecNO_PERMISSION)); + Ter(tecNO_PERMISSION)); env.close(); auto const ag = env.le(keylet::payChan(alice.id(), gw.id(), aseq)); BEAST_EXPECT(ag); { - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ag) != aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ag) != aod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); - ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + xrpl::Dir iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), ag) != iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), ag) != iod.end()); } auto const chan_ag = paychan::channel(alice, gw, aseq); - env(paychan::claim(alice, chan_ag, USD(1'000), USD(1'000)), - txflags(tfClose)); + env(paychan::claim(alice, chan_ag, USD(1'000), USD(1'000)), Txflags(tfClose)); { - BEAST_EXPECT( - !env.le(keylet::payChan(alice.id(), gw.id(), aseq))); + BEAST_EXPECT(!env.le(keylet::payChan(alice.id(), gw.id(), aseq))); - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ag) == aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ag) == aod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); - ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id())); + xrpl::Dir iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 2); - BEAST_EXPECT( - std::find(iod.begin(), iod.end(), ag) == iod.end()); + BEAST_EXPECT(std::find(iod.begin(), iod.end(), ag) == iod.end()); } } } @@ -1000,8 +925,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const preDst = env.balance(t.dst, USD); auto const chan = paychan::channel(t.src, t.dst, seq1); - auto const sig = - paychan::signClaimAuth(pk, t.src.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, t.src.sk(), chan, delta); env(paychan::claim(t.dst, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1035,8 +959,7 @@ struct PayChanToken_test : public beast::unit_test::suite // issuer cannot create paychan auto const pk = gw.pk(); auto const settleDelay = 100s; - env(paychan::create(gw, alice, USD(1'000), settleDelay, pk), - ter(tecNO_PERMISSION)); + env(paychan::create(gw, alice, USD(1'000), settleDelay, pk), Ter(tecNO_PERMISSION)); env.close(); } @@ -1083,10 +1006,8 @@ struct PayChanToken_test : public beast::unit_test::suite // issuer can claim paychan, no dest trustline auto const chan = paychan::channel(t.src, t.dst, seq1); - auto const sig = - paychan::signClaimAuth(pk, t.src.sk(), chan, USD(1'000)); - env(paychan::claim( - t.dst, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + auto const sig = paychan::signClaimAuth(pk, t.src.sk(), chan, USD(1'000)); + env(paychan::claim(t.dst, chan, USD(1'000), USD(1'000), Slice(sig), pk)); env.close(); auto const preAmount = 10'000; BEAST_EXPECT(preSrc == USD(preAmount)); @@ -1132,13 +1053,11 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, delta, settleDelay, pk)); env.close(); auto const transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // bob can claim paychan auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1168,8 +1087,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, delta, settleDelay, pk)); env.close(); auto transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // issuer changes rate higher env(rate(gw, 1.26)); @@ -1177,8 +1095,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob can claim paychan - rate unchanged auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1208,8 +1125,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, delta, settleDelay, pk)); env.close(); auto transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // issuer changes rate lower env(rate(gw, 1.00)); @@ -1217,8 +1133,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob can claim paychan - rate changed auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1249,8 +1164,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, delta, settleDelay, pk)); env.close(); auto transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // issuer changes rate lower env(rate(gw, 1.00)); @@ -1258,7 +1172,7 @@ struct PayChanToken_test : public beast::unit_test::suite // alice can close paychan - rate is not charged auto const chan = paychan::channel(alice, bob, seq1); - env(paychan::claim(bob, chan), txflags(tfClose)); + env(paychan::claim(bob, chan), Txflags(tfClose)); env.close(); BEAST_EXPECT(env.balance(alice, USD) == preAlice); @@ -1301,8 +1215,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob can claim auto const preBobLimit = env.limit(bob, USD); auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); auto const postBobLimit = env.limit(bob, USD); @@ -1332,7 +1245,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(fset(gw, asfAllowTrustLineLocking)); env(fset(gw, asfRequireAuth)); env.close(); - env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth)); + env(trust(gw, aliceUSD(10'000)), Txflags(tfSetfAuth)); env(trust(alice, USD(10'000))); env(trust(bob, USD(10'000))); env.close(); @@ -1344,12 +1257,11 @@ struct PayChanToken_test : public beast::unit_test::suite auto const delta = USD(125); auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, delta, settleDelay, pk), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, delta, settleDelay, pk), Ter(tecNO_AUTH)); env.close(); // set auth on bob - env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth)); + env(trust(gw, bobUSD(10'000)), Txflags(tfSetfAuth)); env(trust(bob, USD(10'000))); env.close(); env(pay(gw, bob, USD(1'000))); @@ -1402,8 +1314,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; // create paychan fails - frozen trustline - env(paychan::create(alice, bob, delta, settleDelay, pk), - ter(tecFROZEN)); + env(paychan::create(alice, bob, delta, settleDelay, pk), Ter(tecFROZEN)); env.close(); // clear global freeze @@ -1421,8 +1332,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob claim paychan success regardless of frozen assets auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1441,7 +1351,7 @@ struct PayChanToken_test : public beast::unit_test::suite // alice close paychan success regardless of frozen assets auto const chan2 = paychan::channel(alice, bob, seq1); - env(paychan::claim(alice, chan2, delta, delta), txflags(tfClose)); + env(paychan::claim(alice, chan2, delta, delta), Txflags(tfClose)); env.close(); } @@ -1470,8 +1380,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; // create paychan fails - frozen trustline - env(paychan::create(alice, bob, delta, settleDelay, pk), - ter(tecFROZEN)); + env(paychan::create(alice, bob, delta, settleDelay, pk), Ter(tecFROZEN)); env.close(); // clear freeze on alice trustline @@ -1489,8 +1398,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob claim paychan success regardless of frozen assets auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -1510,7 +1418,7 @@ struct PayChanToken_test : public beast::unit_test::suite // alice close paychan success regardless of frozen assets auto const chan2 = paychan::channel(alice, bob, seq1); - env(paychan::claim(alice, chan2, delta, delta), txflags(tfClose)); + env(paychan::claim(alice, chan2, delta, delta), Txflags(tfClose)); env.close(); } @@ -1539,13 +1447,11 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; // create paychan fails - frozen trustline - env(paychan::create(alice, bob, delta, settleDelay, pk), - ter(tecFROZEN)); + env(paychan::create(alice, bob, delta, settleDelay, pk), Ter(tecFROZEN)); env.close(); // clear freeze on alice trustline - env(trust( - gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); + env(trust(gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); env.close(); // create paychan success @@ -1559,15 +1465,12 @@ struct PayChanToken_test : public beast::unit_test::suite // bob claim paychan fails because of deep frozen assets auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); - env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk), - ter(tecFROZEN)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); + env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk), Ter(tecFROZEN)); env.close(); // reset freeze on alice and bob trustline - env(trust( - gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); + env(trust(gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze)); env(trust(gw, USD(10'000), bob, tfClearFreeze | tfClearDeepFreeze)); env.close(); @@ -1582,7 +1485,7 @@ struct PayChanToken_test : public beast::unit_test::suite // bob close paychan success regardless of deep frozen assets auto const chan2 = paychan::channel(alice, bob, seq1); - env(paychan::claim(bob, chan2), txflags(tfClose)); + env(paychan::claim(bob, chan2), Txflags(tfClose)); env.close(); } } @@ -1619,7 +1522,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; env(paychan::create(alice, bob, delta, settleDelay, pk)); env.close(); - env(pay(alice, gw, USD(10'000)), ter(tecPATH_PARTIAL)); + env(pay(alice, gw, USD(10'000)), Ter(tecPATH_PARTIAL)); } { // test tecINSUFFICIENT_FUNDS @@ -1642,7 +1545,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); env(paychan::create(alice, bob, USD(10'000), settleDelay, pk), - ter(tecINSUFFICIENT_FUNDS)); + Ter(tecINSUFFICIENT_FUNDS)); env.close(); } } @@ -1675,8 +1578,7 @@ struct PayChanToken_test : public beast::unit_test::suite // alice cannot create paychan for 1/10 iou - precision loss auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, USD(1), settleDelay, pk), - ter(tecPRECISION_LOSS)); + env(paychan::create(alice, bob, USD(1), settleDelay, pk), Ter(tecPRECISION_LOSS)); env.close(); auto const seq1 = env.seq(alice); @@ -1686,10 +1588,8 @@ struct PayChanToken_test : public beast::unit_test::suite // bob claim paychan success auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); - env(paychan::claim( - bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim(bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); env.close(); } } @@ -1704,8 +1604,7 @@ struct PayChanToken_test : public beast::unit_test::suite for (bool const withTokenPaychan : {false, true}) { - auto const amend = - withTokenPaychan ? features : features - featureTokenPaychan; + auto const amend = withTokenPaychan ? features : features - featureTokenPaychan; Env env{*this, amend}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1714,28 +1613,23 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); env.close(); - auto const openResult = - withTokenPaychan ? ter(tesSUCCESS) : ter(temBAD_AMOUNT); - auto const closeResult = - withTokenPaychan ? ter(tesSUCCESS) : ter(tecNO_TARGET); + auto const openResult = withTokenPaychan ? Ter(tesSUCCESS) : Ter(temBAD_AMOUNT); + auto const closeResult = withTokenPaychan ? Ter(tesSUCCESS) : Ter(tecNO_TARGET); auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), - openResult); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), openResult); env.close(); env(paychan::fund(alice, chan, MPT(1'000)), openResult); env.close(); - env(paychan::claim(bob, chan), txflags(tfClose), closeResult); + env(paychan::claim(bob, chan), Txflags(tfClose), closeResult); env.close(); } } @@ -1749,22 +1643,20 @@ struct PayChanToken_test : public beast::unit_test::suite for (bool const withMPT : {true, false}) { - auto const amend = - withMPT ? features : features - featureMPTokensV1; + auto const amend = withMPT ? features : features - featureMPTokensV1; Env env{*this, amend}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); env.fund(XRP(1'000), alice, bob, gw); - Json::Value jv = - paychan::create(alice, bob, XRP(1), 100s, alice.pk()); + json::Value jv = paychan::create(alice, bob, XRP(1), 100s, alice.pk()); jv.removeMember(jss::Amount); jv[jss::Amount][jss::mpt_issuance_id] = "00000004A407AF5856CCF3C42619DAA925813FC955C72983"; jv[jss::Amount][jss::value] = "-1"; - auto const result = withMPT ? ter(temBAD_AMOUNT) : ter(temDISABLED); + auto const result = withMPT ? Ter(temBAD_AMOUNT) : Ter(temDISABLED); env(jv, result); env.close(); } @@ -1778,9 +1670,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -1790,8 +1680,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(-1), settleDelay, pk), - ter(temBAD_AMOUNT)); + env(paychan::create(alice, bob, MPT(-1), settleDelay, pk), Ter(temBAD_AMOUNT)); env.close(); } } @@ -1811,9 +1700,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -1821,8 +1708,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = gw.pk(); auto const settleDelay = 100s; - env(paychan::create(gw, alice, MPT(1), settleDelay, pk), - ter(tecNO_PERMISSION)); + env(paychan::create(gw, alice, MPT(1), settleDelay, pk), Ter(tecNO_PERMISSION)); env.close(); } @@ -1835,13 +1721,11 @@ struct PayChanToken_test : public beast::unit_test::suite env.fund(XRP(10'000), alice, bob, gw); env.close(); - auto const mpt = ripple::test::jtx::MPT( - alice.name(), makeMptID(env.seq(alice), alice)); - Json::Value jv = - paychan::create(alice, bob, mpt(2), 100s, alice.pk()); + auto const mpt = xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice)); + json::Value jv = paychan::create(alice, bob, mpt(2), 100s, alice.pk()); jv[jss::Amount][jss::mpt_issuance_id] = "00000004A407AF5856CCF3C42619DAA925813FC955C72983"; - env(jv, ter(tecOBJECT_NOT_FOUND)); + env(jv, Ter(tecOBJECT_NOT_FOUND)); env.close(); } @@ -1853,8 +1737,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const gw = Account("gw"); MPTTester mptGw(env, gw, {.holders = {alice, bob}}); - mptGw.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -1864,8 +1747,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(3), settleDelay, pk), - ter(tecNO_PERMISSION)); + env(paychan::create(alice, bob, MPT(3), settleDelay, pk), Ter(tecNO_PERMISSION)); env.close(); } @@ -1878,15 +1760,12 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); auto const MPT = mptGw["MPT"]; auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(4), settleDelay, pk), - ter(tecOBJECT_NOT_FOUND)); + env(paychan::create(alice, bob, MPT(4), settleDelay, pk), Ter(tecOBJECT_NOT_FOUND)); env.close(); } @@ -1901,8 +1780,7 @@ struct PayChanToken_test : public beast::unit_test::suite mptGw.create( {.ownerCount = 1, .holderCount = 0, - .flags = - tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = gw, .holder = alice}); auto const MPT = mptGw["MPT"]; @@ -1910,13 +1788,11 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // unauthorize account - mptGw.authorize( - {.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + mptGw.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(5), settleDelay, pk), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, MPT(5), settleDelay, pk), Ter(tecNO_AUTH)); env.close(); } @@ -1931,8 +1807,7 @@ struct PayChanToken_test : public beast::unit_test::suite mptGw.create( {.ownerCount = 1, .holderCount = 0, - .flags = - tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = gw, .holder = alice}); mptGw.authorize({.account = bob}); @@ -1943,13 +1818,11 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // unauthorize dest - mptGw.authorize( - {.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); + mptGw.authorize({.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(6), settleDelay, pk), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, MPT(6), settleDelay, pk), Ter(tecNO_AUTH)); env.close(); } @@ -1977,8 +1850,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(7), settleDelay, pk), - ter(tecLOCKED)); + env(paychan::create(alice, bob, MPT(7), settleDelay, pk), Ter(tecLOCKED)); env.close(); } @@ -2006,8 +1878,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(8), settleDelay, pk), - ter(tecLOCKED)); + env(paychan::create(alice, bob, MPT(8), settleDelay, pk), Ter(tecLOCKED)); env.close(); } @@ -2019,8 +1890,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const gw = Account("gw"); MPTTester mptGw(env, gw, {.holders = {alice, bob}}); - mptGw.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); + mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -2030,8 +1900,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(9), settleDelay, pk), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, MPT(9), settleDelay, pk), Ter(tecNO_AUTH)); env.close(); } @@ -2044,9 +1913,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -2055,8 +1922,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(11), settleDelay, pk), - ter(tecINSUFFICIENT_FUNDS)); + env(paychan::create(alice, bob, MPT(11), settleDelay, pk), Ter(tecINSUFFICIENT_FUNDS)); env.close(); } @@ -2069,9 +1935,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -2081,8 +1945,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(11), settleDelay, pk), - ter(tecINSUFFICIENT_FUNDS)); + env(paychan::create(alice, bob, MPT(11), settleDelay, pk), Ter(tecINSUFFICIENT_FUNDS)); env.close(); } } @@ -2105,8 +1968,7 @@ struct PayChanToken_test : public beast::unit_test::suite mptGw.create( {.ownerCount = 1, .holderCount = 0, - .flags = - tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = gw, .holder = alice}); mptGw.authorize({.account = bob}); @@ -2119,18 +1981,14 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(10), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), Ter(tesSUCCESS)); env.close(); // unauthorize dest - mptGw.authorize( - {.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); + mptGw.authorize({.account = gw, .holder = bob, .flags = tfMPTUnauthorize}); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); - env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), - ter(tecNO_AUTH)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), Ter(tecNO_AUTH)); env.close(); } @@ -2156,17 +2014,14 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(8), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(8), settleDelay, pk), Ter(tesSUCCESS)); env.close(); // lock dest mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock}); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(8)); - env(paychan::claim(bob, chan, MPT(8), MPT(8), Slice(sig), pk), - ter(tecLOCKED)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(8)); + env(paychan::claim(bob, chan, MPT(8), MPT(8), Slice(sig), pk), Ter(tecLOCKED)); env.close(); } } @@ -2192,9 +2047,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -2203,14 +2056,12 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(10), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), Ter(tesSUCCESS)); env.close(); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), - ter(tecINSUFFICIENT_RESERVE)); + Ter(tecINSUFFICIENT_RESERVE)); env.close(); } @@ -2225,9 +2076,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -2236,14 +2085,11 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(10), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), Ter(tesSUCCESS)); env.close(); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); - env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), Ter(tesSUCCESS)); env.close(); } @@ -2258,9 +2104,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -2269,12 +2113,10 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(10), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), Ter(tesSUCCESS)); env.close(); - env(paychan::claim(alice, chan, MPT(10), MPT(10)), - ter(tecNO_PERMISSION)); + env(paychan::claim(alice, chan, MPT(10), MPT(10)), Ter(tecNO_PERMISSION)); env.close(); } } @@ -2296,9 +2138,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, carol}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = carol}); auto const MPT = mptGw["MPT"]; @@ -2315,8 +2155,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const preBobMPT = env.balance(bob, MPT); auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); @@ -2330,11 +2169,8 @@ struct PayChanToken_test : public beast::unit_test::suite auto const preAliceMPT = env.balance(alice, MPT); auto const preBobMPT = env.balance(bob, MPT); auto const pk = alice.pk(); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); - env(paychan::claim( - bob, chan, MPT(1'000), MPT(1'000), Slice(sig), pk), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim(bob, chan, MPT(1'000), MPT(1'000), Slice(sig), pk), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT); @@ -2352,8 +2188,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const preBobMPT = env.balance(bob, MPT); auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); @@ -2367,8 +2202,8 @@ struct PayChanToken_test : public beast::unit_test::suite auto const preAliceMPT = env.balance(alice, MPT); auto const preBobMPT = env.balance(bob, MPT); env(paychan::claim(alice, chan2, MPT(1'000), MPT(1'000)), - txflags(tfClose), - ter(tesSUCCESS)); + Txflags(tfClose), + Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT); @@ -2387,12 +2222,10 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const pk2 = carol.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), Ter(tesSUCCESS)); env.close(); - env(paychan::create(carol, bob, MPT(1'000), settleDelay, pk2), - ter(tesSUCCESS)); + env(paychan::create(carol, bob, MPT(1'000), settleDelay, pk2), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); @@ -2414,13 +2247,11 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; - env(pay(gw, alice, MPT(maxMPTokenAmount))); + env(pay(gw, alice, MPT(kMAX_MP_TOKEN_AMOUNT))); env.close(); auto const preAliceMPT = env.balance(alice, MPT); @@ -2440,22 +2271,19 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1)); - env(paychan::claim(bob, chan, MPT(1), MPT(1), Slice(sig), pk), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1)); + env(paychan::claim(bob, chan, MPT(1), MPT(1), Slice(sig), pk), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1)); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); - BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), alice)) - ->isFieldPresent(sfLockedAmount)); + BEAST_EXPECT( + !env.le(keylet::mptoken(MPT.mpt(), alice))->isFieldPresent(sfLockedAmount)); BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1)); BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); - BEAST_EXPECT(!env.le(keylet::mptIssuance(MPT.mpt())) - ->isFieldPresent(sfLockedAmount)); + BEAST_EXPECT(!env.le(keylet::mptIssuance(MPT.mpt()))->isFieldPresent(sfLockedAmount)); } // Max MPT Amount Issued (PayChan Max MPT) @@ -2467,13 +2295,11 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; - env(pay(gw, alice, MPT(maxMPTokenAmount))); + env(pay(gw, alice, MPT(kMAX_MP_TOKEN_AMOUNT))); env.close(); auto const preAliceMPT = env.balance(alice, MPT); @@ -2484,8 +2310,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan1 = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create( - alice, bob, MPT(maxMPTokenAmount - 10), settleDelay, pk)); + env(paychan::create(alice, bob, MPT(kMAX_MP_TOKEN_AMOUNT - 10), settleDelay, pk)); env.close(); // PayChan 10 MPT @@ -2493,37 +2318,32 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, MPT(10), settleDelay, pk)); env.close(); - BEAST_EXPECT( - env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount)); - BEAST_EXPECT(mptEscrowed(env, alice, MPT) == maxMPTokenAmount); + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(kMAX_MP_TOKEN_AMOUNT)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == kMAX_MP_TOKEN_AMOUNT); BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT); BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); - BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == maxMPTokenAmount); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == kMAX_MP_TOKEN_AMOUNT); - auto const sig1 = paychan::signClaimAuth( - pk, alice.sk(), chan1, MPT(maxMPTokenAmount - 10)); + auto const sig1 = + paychan::signClaimAuth(pk, alice.sk(), chan1, MPT(kMAX_MP_TOKEN_AMOUNT - 10)); env(paychan::claim( bob, chan1, - MPT(maxMPTokenAmount - 10), - MPT(maxMPTokenAmount - 10), + MPT(kMAX_MP_TOKEN_AMOUNT - 10), + MPT(kMAX_MP_TOKEN_AMOUNT - 10), Slice(sig1), pk), - ter(tesSUCCESS)); + Ter(tesSUCCESS)); env.close(); - auto const sig2 = - paychan::signClaimAuth(pk, alice.sk(), chan2, MPT(10)); - env(paychan::claim(bob, chan2, MPT(10), MPT(10), Slice(sig2), pk), - ter(tesSUCCESS)); + auto const sig2 = paychan::signClaimAuth(pk, alice.sk(), chan2, MPT(10)); + env(paychan::claim(bob, chan2, MPT(10), MPT(10), Slice(sig2), pk), Ter(tesSUCCESS)); env.close(); - BEAST_EXPECT( - env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount)); + BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(kMAX_MP_TOKEN_AMOUNT)); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); - BEAST_EXPECT( - env.balance(bob, MPT) == preBobMPT + MPT(maxMPTokenAmount)); + BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(kMAX_MP_TOKEN_AMOUNT)); BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); @@ -2546,9 +2366,7 @@ struct PayChanToken_test : public beast::unit_test::suite Env env{*this, features}; MPTTester mptGw(env, gw, {.holders = {alice, bob, carol}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); mptGw.authorize({.account = carol}); @@ -2565,13 +2383,11 @@ struct PayChanToken_test : public beast::unit_test::suite auto const settleDelay = 100s; env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk)); BEAST_EXPECT( - (*env.meta())[sfTransactionResult] == - static_cast(tesSUCCESS)); + (*env.meta())[sfTransactionResult] == static_cast(tesSUCCESS)); env.close(); env(paychan::create(bob, carol, MPT(1'000), settleDelay, pk2)); BEAST_EXPECT( - (*env.meta())[sfTransactionResult] == - static_cast(tesSUCCESS)); + (*env.meta())[sfTransactionResult] == static_cast(tesSUCCESS)); env.close(); auto const ab = env.le(keylet::payChan(alice.id(), bob.id(), aseq)); @@ -2581,72 +2397,56 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(bc); { - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) != aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) != aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) != bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); - BEAST_EXPECT( - std::find(cod.begin(), cod.end(), bc) != cod.end()); + BEAST_EXPECT(std::find(cod.begin(), cod.end(), bc) != cod.end()); } auto const chan_ab = paychan::channel(alice, bob, aseq); - env(paychan::claim(alice, chan_ab, MPT(1'000), MPT(1'000)), - txflags(tfClose)); + env(paychan::claim(alice, chan_ab, MPT(1'000), MPT(1'000)), Txflags(tfClose)); { - BEAST_EXPECT( - !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); - BEAST_EXPECT( - env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + BEAST_EXPECT(!env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT(env.le(keylet::payChan(bob.id(), carol.id(), bseq))); - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); } env.close(); auto const chan_bc = paychan::channel(bob, carol, bseq); - env(paychan::claim(bob, chan_bc, MPT(1'000), MPT(1'000)), - txflags(tfClose)); + env(paychan::claim(bob, chan_bc, MPT(1'000), MPT(1'000)), Txflags(tfClose)); { - BEAST_EXPECT( - !env.le(keylet::payChan(alice.id(), bob.id(), aseq))); - BEAST_EXPECT( - !env.le(keylet::payChan(bob.id(), carol.id(), bseq))); + BEAST_EXPECT(!env.le(keylet::payChan(alice.id(), bob.id(), aseq))); + BEAST_EXPECT(!env.le(keylet::payChan(bob.id(), carol.id(), bseq))); - ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id())); + xrpl::Dir aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT( - std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); - ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id())); + xrpl::Dir bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT( - std::find(bod.begin(), bod.end(), bc) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) == bod.end()); - ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id())); + xrpl::Dir cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); } } @@ -2667,9 +2467,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -2678,8 +2476,7 @@ struct PayChanToken_test : public beast::unit_test::suite // issuer cannot create paychan auto const pk = gw.pk(); auto const settleDelay = 100s; - env(paychan::create(gw, alice, MPT(1'000), settleDelay, pk), - ter(tecNO_PERMISSION)); + env(paychan::create(gw, alice, MPT(1'000), settleDelay, pk), Ter(tecNO_PERMISSION)); env.close(); } @@ -2691,9 +2488,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -2718,10 +2513,8 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed + 1'000); // issuer (dest) can claim paychan - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); - env(paychan::claim( - gw, chan, MPT(1'000), MPT(1'000), Slice(sig), pk)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim(gw, chan, MPT(1'000), MPT(1'000), Slice(sig), pk)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); @@ -2767,13 +2560,11 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, MPT(125), settleDelay, pk)); env.close(); auto const transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // bob can claim paychan auto const chan = paychan::channel(alice, bob, seq1); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, delta); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, delta); env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); @@ -2811,12 +2602,11 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, MPT(125), settleDelay, pk)); env.close(); auto const transferRate = paychan::rate(env, alice, bob, seq1); - BEAST_EXPECT( - transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); // bob can close paychan auto const chan = paychan::channel(alice, bob, seq1); - env(paychan::claim(bob, chan), txflags(tfClose)); + env(paychan::claim(bob, chan), Txflags(tfClose)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == preAlice); @@ -2856,14 +2646,14 @@ struct PayChanToken_test : public beast::unit_test::suite // env(escrow::create(alice, bob, MPT(100)), // escrow::condition(escrow::cb1), // escrow::finish_time(env.now() + 1s), - // fee(baseFee * 150)); + // Fee(baseFee * 150)); // env.close(); // // bob can finish escrow - is authorized // env(escrow::finish(bob, alice, seq), // escrow::condition(escrow::cb1), // escrow::fulfillment(escrow::fb1), - // fee(baseFee * 150)); + // Fee(baseFee * 150)); // env.close(); // } @@ -2904,12 +2694,11 @@ struct PayChanToken_test : public beast::unit_test::suite // bob cannot claim auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(100)); - env(paychan::claim(bob, chan, MPT(100), MPT(100), Slice(sig), pk), - ter(tecLOCKED)); + env(paychan::claim(bob, chan, MPT(100), MPT(100), Slice(sig), pk), Ter(tecLOCKED)); env.close(); // bob can claim/close - env(paychan::claim(bob, chan), txflags(tfClose)); + env(paychan::claim(bob, chan), Txflags(tfClose)); env.close(); } @@ -2926,8 +2715,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const gw = Account("gw"); MPTTester mptGw(env, gw, {.holders = {alice, bob}}); - mptGw.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); + mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -2938,8 +2726,7 @@ struct PayChanToken_test : public beast::unit_test::suite // alice cannot create paychan to non issuer auto const pk = alice.pk(); auto const settleDelay = 100s; - env(paychan::create(alice, bob, MPT(100), settleDelay, pk), - ter(tecNO_AUTH)); + env(paychan::create(alice, bob, MPT(100), settleDelay, pk), Ter(tecNO_AUTH)); env.close(); // PayChan Create & Claim @@ -2950,8 +2737,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // gw can claim - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(100)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(100)); env(paychan::claim(gw, chan, MPT(100), MPT(100), Slice(sig), pk)); env.close(); } @@ -2964,7 +2750,7 @@ struct PayChanToken_test : public beast::unit_test::suite env.close(); // gw can claim/close - env(paychan::claim(gw, chan), txflags(tfClose)); + env(paychan::claim(gw, chan), Txflags(tfClose)); env.close(); } } @@ -2985,9 +2771,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice, bob}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); mptGw.authorize({.account = bob}); auto const MPT = mptGw["MPT"]; @@ -3001,7 +2785,7 @@ struct PayChanToken_test : public beast::unit_test::suite env(paychan::create(alice, bob, MPT(10), settleDelay, pk)); env.close(); - env(pay(alice, gw, MPT(10'000)), ter(tecPATH_PARTIAL)); + env(pay(alice, gw, MPT(10'000)), Ter(tecPATH_PARTIAL)); env(pay(alice, gw, MPT(9'990))); env(pay(bob, gw, MPT(10'000))); BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); @@ -3010,15 +2794,10 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); BEAST_EXPECT(env.balance(gw, MPT) == MPT(10)); mptGw.authorize({.account = bob, .flags = tfMPTUnauthorize}); - mptGw.destroy( - {.id = mptGw.issuanceID(), - .ownerCount = 1, - .err = tecHAS_OBLIGATIONS}); + mptGw.destroy({.id = mptGw.issuanceID(), .ownerCount = 1, .err = tecHAS_OBLIGATIONS}); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); - env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), Ter(tesSUCCESS)); env.close(); env(pay(bob, gw, MPT(10))); @@ -3036,9 +2815,7 @@ struct PayChanToken_test : public beast::unit_test::suite MPTTester mptGw(env, gw, {.holders = {alice}}); mptGw.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); mptGw.authorize({.account = alice}); auto const MPT = mptGw["MPT"]; env(pay(gw, alice, MPT(10'000))); @@ -3047,8 +2824,7 @@ struct PayChanToken_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create(alice, bob, MPT(10), settleDelay, pk), - ter(tesSUCCESS)); + env(paychan::create(alice, bob, MPT(10), settleDelay, pk), Ter(tesSUCCESS)); env.close(); env(pay(alice, gw, MPT(9'990))); @@ -3057,14 +2833,10 @@ struct PayChanToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10); mptGw.authorize( - {.account = alice, - .flags = tfMPTUnauthorize, - .err = tecHAS_OBLIGATIONS}); + {.account = alice, .flags = tfMPTUnauthorize, .err = tecHAS_OBLIGATIONS}); - auto const sig = - paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); - env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), - ter(tesSUCCESS)); + auto const sig = paychan::signClaimAuth(pk, alice.sk(), chan, MPT(10)); + env(paychan::claim(bob, chan, MPT(10), MPT(10), Slice(sig), pk), Ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); @@ -3120,12 +2892,12 @@ struct PayChanToken_test : public beast::unit_test::suite run() override { using namespace test::jtx; - FeatureBitset const all{testable_amendments()}; + FeatureBitset const all{testableAmendments()}; testIOUWithFeats(all); testMPTWithFeats(all); } }; -BEAST_DEFINE_TESTSUITE(PayChanToken, app, ripple); +BEAST_DEFINE_TESTSUITE(PayChanToken, app, xrpl); } // namespace test -} // namespace ripple +} // namespace xrpl diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 486b5149de4..cf9773fe5c4 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -167,10 +167,8 @@ struct PayChan_test : public beast::unit_test::Suite auto const authAmt = reqBal + XRP(100); assert(reqBal <= chanAmt); env(paychan::claim(alice, chan, reqBal, authAmt)); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } @@ -331,16 +329,13 @@ struct PayChan_test : public beast::unit_test::Suite env.current()->header().parentCloseTime + 3600s; auto const channelFunds = XRP(1000); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create( - alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); env.close(cancelAfter); { // dst cannot claim after cancelAfter - auto const chanBal = - paychan::channelBalance(*env.current(), chan); - auto const chanAmt = - paychan::channelAmount(*env.current(), chan); + auto const chanBal = paychan::channelBalance(*env.current(), chan); + auto const chanAmt = paychan::channelAmount(*env.current(), chan); auto preAlice = env.balance(alice); auto preBob = env.balance(bob); auto const delta = XRP(500); @@ -365,8 +360,7 @@ struct PayChan_test : public beast::unit_test::Suite env.current()->header().parentCloseTime + 3600s; auto const channelFunds = XRP(1000); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create( - alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); // third party close before cancelAfter env(claim(carol, chan), Txflags(tfClose), Ter(tecNO_PERMISSION)); @@ -435,8 +429,7 @@ struct PayChan_test : public beast::unit_test::Suite NetClock::time_point const cancelAfter = closeTime + 7200s; auto const channelFunds = XRP(1000); auto const chan = paychan::channel(alice, bob, env.seq(alice)); - env(paychan::create( - alice, bob, channelFunds, settleDelay, pk, cancelAfter)); + env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter)); BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); // Owner closes, will close after settleDelay @@ -466,8 +459,7 @@ struct PayChan_test : public beast::unit_test::Suite env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration - 50s}), Ter(temBAD_EXPIRATION)); BEAST_EXPECT(!channelExpiration(*env.current(), chan)); - env(paychan::fund( - alice, chan, XRP(1), NetClock::time_point{minExpiration})); + env(paychan::fund(alice, chan, XRP(1), NetClock::time_point{minExpiration})); env.close(minExpiration); // Try to extend the expiration after the expiration has already passed env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration + 1000s})); @@ -554,10 +546,8 @@ struct PayChan_test : public beast::unit_test::Suite { // claim the entire amount auto const preBob = env.balance(bob); - env(paychan::claim( - alice, chan, channelFunds.value(), channelFunds.value())); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == channelFunds); + env(paychan::claim(alice, chan, channelFunds.value(), channelFunds.value())); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == channelFunds); BEAST_EXPECT(env.balance(bob) == preBob + channelFunds); } auto const preAlice = env.balance(alice); @@ -710,10 +700,8 @@ struct PayChan_test : public beast::unit_test::Suite env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000)); // alice can add more funds to the channel even though bob has // asfDepositAuth set. @@ -1015,8 +1003,7 @@ struct PayChan_test : public beast::unit_test::Suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = - to_string(paychan::channel(alice, bob, env.seq(alice))); + auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice))); env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { @@ -1073,8 +1060,7 @@ struct PayChan_test : public beast::unit_test::Suite BEAST_EXPECT(r[jss::result][jss::channels].size() == 0); BEAST_EXPECT(r[jss::result][jss::validated]); } - auto const chan2Str = - to_string(paychan::channel(alice, bob, env.seq(alice))); + auto const chan2Str = to_string(paychan::channel(alice, bob, env.seq(alice))); env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { @@ -1125,8 +1111,7 @@ struct PayChan_test : public beast::unit_test::Suite auto const channelFunds = XRP(1); for (auto const& b : bobs) { - env(paychan::create( - alice, b, channelFunds, settleDelay, alice.pk())); + env(paychan::create(alice, b, channelFunds, settleDelay, alice.pk())); } } @@ -1253,8 +1238,7 @@ struct PayChan_test : public beast::unit_test::Suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = - to_string(paychan::channel(alice, bob, env.seq(alice))); + auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice))); env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); @@ -1289,8 +1273,7 @@ struct PayChan_test : public beast::unit_test::Suite auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = XRP(1000); - auto const chan1Str = - to_string(paychan::channel(alice, bob, env.seq(alice))); + auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice))); env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); std::string chan1PkStr; @@ -1313,8 +1296,7 @@ struct PayChan_test : public beast::unit_test::Suite BEAST_EXPECT(r[jss::result][jss::channels].size() == 0); BEAST_EXPECT(r[jss::result][jss::validated]); } - auto const chan2Str = - to_string(paychan::channel(alice, bob, env.seq(alice))); + auto const chan2Str = to_string(paychan::channel(alice, bob, env.seq(alice))); env(paychan::create(alice, bob, channelFunds, settleDelay, pk)); env.close(); { @@ -1770,10 +1752,8 @@ struct PayChan_test : public beast::unit_test::Suite auto const chan = paychan::channel(alice, bob, env.seq(alice)); env(paychan::create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0)); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000)); rmAccount(env, alice, carol, tecHAS_OBLIGATIONS); rmAccount(env, bob, carol, TER(tecHAS_OBLIGATIONS)); @@ -1882,10 +1862,8 @@ struct PayChan_test : public beast::unit_test::Suite env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob + delta); chanBal = reqBal; } @@ -1903,10 +1881,8 @@ struct PayChan_test : public beast::unit_test::Suite env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == reqBal); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); auto const feeDrops = env.current()->fees().base; BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); chanBal = reqBal; @@ -1921,10 +1897,8 @@ struct PayChan_test : public beast::unit_test::Suite env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); } { @@ -1943,10 +1917,8 @@ struct PayChan_test : public beast::unit_test::Suite env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); BEAST_EXPECT(env.seq(bob) == bobSeq); - BEAST_EXPECT( - paychan::channelBalance(*env.current(), chan) == chanBal); - BEAST_EXPECT( - paychan::channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob) == preBob); } diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 4952d60dc1f..101d774fefe 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -348,10 +349,7 @@ ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); /* Token (IOU/MPT) Locking */ /******************************************************************************/ uint64_t -mptEscrowed( - jtx::Env const& env, - jtx::Account const& account, - jtx::MPT const& mpt); +mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt); uint64_t issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt); @@ -569,65 +567,6 @@ accountBalance(Env& env, Account const& acct); [[nodiscard]] bool expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedValue); -/* Payment Channel */ -/******************************************************************************/ -namespace paychan { - -json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt); - -inline json::Value -create( - Account const& account, - Account const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt) -{ - return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); -} - -json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration = std::nullopt); - -json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance = std::nullopt, - std::optional const& amount = std::nullopt, - std::optional const& signature = std::nullopt, - std::optional const& pk = std::nullopt); - -uint256 -channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue); - -inline uint256 -channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) -{ - return channel(account.id(), dst.id(), seqProxyValue); -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan); - -bool -channelExists(ReadView const& view, uint256 const& chan); - -} // namespace paychan /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index f36e4ee29bd..c9a3543e21d 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -97,10 +97,7 @@ ownerCount(Env const& env, Account const& account) /* Token (IOU/MPT) Locking */ /******************************************************************************/ uint64_t -mptEscrowed( - jtx::Env const& env, - jtx::Account const& account, - jtx::MPT const& mpt) +mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt) { auto const sle = env.le(keylet::mptoken(mpt.mpt(), account)); if (sle && sle->isFieldPresent(sfLockedAmount)) @@ -120,12 +117,11 @@ issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt) jtx::PrettyAmount issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue) { - Json::Value params; + json::Value params; params[jss::account] = account.human(); auto jrr = env.rpc("json", "gateway_balances", to_string(params)); auto const result = jrr[jss::result]; - auto const obligations = - result[jss::obligations][to_string(issue.currency)]; + auto const obligations = result[jss::obligations][to_string(issue.currency)]; if (obligations.isNull()) return {STAmount(issue, 0), account.name()}; STAmount const amount = amountFromString(issue, obligations.asString()); @@ -135,7 +131,7 @@ issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue) jtx::PrettyAmount issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue) { - Json::Value params; + json::Value params; params[jss::account] = account.human(); auto jrr = env.rpc("json", "gateway_balances", to_string(params)); auto const result = jrr[jss::result]; @@ -533,99 +529,6 @@ expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedVal return accountBalance(env, acct) == to_string(expectedValue.xrp()); } -/* Payment Channel */ -/******************************************************************************/ -namespace paychan { - -json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter, - std::optional const& dstTag) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Account] = to_string(account); - jv[jss::Destination] = to_string(to); - jv[jss::Amount] = amount.getJson(JsonOptions::KNone); - jv[jss::SettleDelay] = settleDelay.count(); - jv[sfPublicKey.fieldName] = strHex(pk.slice()); - if (cancelAfter) - jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count(); - if (dstTag) - jv[sfDestinationTag.fieldName] = *dstTag; - return jv; -} - -json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelFund; - jv[jss::Account] = to_string(account); - jv[sfChannel.fieldName] = to_string(channel); - jv[jss::Amount] = amount.getJson(JsonOptions::KNone); - if (expiration) - jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); - return jv; -} - -json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance, - std::optional const& amount, - std::optional const& signature, - std::optional const& pk) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelClaim; - jv[jss::Account] = to_string(account); - jv["Channel"] = to_string(channel); - if (amount) - jv[jss::Amount] = amount->getJson(JsonOptions::KNone); - if (balance) - jv["Balance"] = balance->getJson(JsonOptions::KNone); - if (signature) - jv["Signature"] = strHex(*signature); - if (pk) - jv["PublicKey"] = strHex(pk->slice()); - return jv; -} - -uint256 -channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue) -{ - auto const k = keylet::payChan(account, dst, seqProxyValue); - return k.key; -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - if (!slep) - return XRPAmount{-1}; - return (*slep)[sfBalance]; -} - -bool -channelExists(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - return bool(slep); -} - -} // namespace paychan /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/paychan.cpp b/src/test/jtx/impl/paychan.cpp index a44fa5acf62..5d274e6500c 100644 --- a/src/test/jtx/impl/paychan.cpp +++ b/src/test/jtx/impl/paychan.cpp @@ -23,14 +23,14 @@ #include #include -namespace ripple { +namespace xrpl { namespace test { namespace jtx { /** Paychan operations. */ namespace paychan { -Json::Value +json::Value create( AccountID const& account, AccountID const& to, @@ -40,12 +40,12 @@ create( std::optional const& cancelAfter, std::optional const& dstTag) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::PaymentChannelCreate; jv[jss::Flags] = tfFullyCanonicalSig; jv[jss::Account] = to_string(account); jv[jss::Destination] = to_string(to); - jv[jss::Amount] = amount.getJson(JsonOptions::none); + jv[jss::Amount] = amount.getJson(JsonOptions::KNone); jv[jss::SettleDelay] = settleDelay.count(); jv[sfPublicKey.fieldName] = strHex(pk.slice()); if (cancelAfter) @@ -55,25 +55,25 @@ create( return jv; } -Json::Value +json::Value fund( AccountID const& account, uint256 const& channel, STAmount const& amount, std::optional const& expiration) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::PaymentChannelFund; jv[jss::Flags] = tfFullyCanonicalSig; jv[jss::Account] = to_string(account); jv[sfChannel.fieldName] = to_string(channel); - jv[jss::Amount] = amount.getJson(JsonOptions::none); + jv[jss::Amount] = amount.getJson(JsonOptions::KNone); if (expiration) jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); return jv; } -Json::Value +json::Value claim( AccountID const& account, uint256 const& channel, @@ -82,15 +82,15 @@ claim( std::optional const& signature, std::optional const& pk) { - Json::Value jv; + json::Value jv; jv[jss::TransactionType] = jss::PaymentChannelClaim; jv[jss::Flags] = tfFullyCanonicalSig; jv[jss::Account] = to_string(account); jv["Channel"] = to_string(channel); if (amount) - jv[jss::Amount] = amount->getJson(JsonOptions::none); + jv[jss::Amount] = amount->getJson(JsonOptions::KNone); if (balance) - jv["Balance"] = balance->getJson(JsonOptions::none); + jv["Balance"] = balance->getJson(JsonOptions::KNone); if (signature) jv["Signature"] = strHex(*signature); if (pk) @@ -99,10 +99,7 @@ claim( } uint256 -channel( - AccountID const& account, - AccountID const& dst, - std::uint32_t seqProxyValue) +channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue) { auto const k = keylet::payChan(account, dst, seqProxyValue); return k.key; @@ -146,15 +143,11 @@ signClaimAuth( } Rate -rate( - Env& env, - Account const& account, - Account const& dest, - std::uint32_t const& seq) +rate(Env& env, Account const& account, Account const& dest, std::uint32_t const& seq) { auto const sle = env.le(keylet::payChan(account.id(), dest.id(), seq)); if (sle->isFieldPresent(sfTransferRate)) - return ripple::Rate((*sle)[sfTransferRate]); + return xrpl::Rate((*sle)[sfTransferRate]); return Rate{0}; } @@ -163,4 +156,4 @@ rate( } // namespace jtx } // namespace test -} // namespace ripple +} // namespace xrpl diff --git a/src/test/jtx/paychan.h b/src/test/jtx/paychan.h index cf6214d6883..22a686d241b 100644 --- a/src/test/jtx/paychan.h +++ b/src/test/jtx/paychan.h @@ -27,14 +27,14 @@ #include -namespace ripple { +namespace xrpl { namespace test { namespace jtx { /** Paychan operations. */ namespace paychan { -Json::Value +json::Value create( AccountID const& account, AccountID const& to, @@ -44,7 +44,7 @@ create( std::optional const& cancelAfter = std::nullopt, std::optional const& dstTag = std::nullopt); -inline Json::Value +inline json::Value create( Account const& account, Account const& to, @@ -54,18 +54,17 @@ create( std::optional const& cancelAfter = std::nullopt, std::optional const& dstTag = std::nullopt) { - return create( - account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); + return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); } -Json::Value +json::Value fund( AccountID const& account, uint256 const& channel, STAmount const& amount, std::optional const& expiration = std::nullopt); -Json::Value +json::Value claim( AccountID const& account, uint256 const& channel, @@ -75,10 +74,7 @@ claim( std::optional const& pk = std::nullopt); uint256 -channel( - AccountID const& account, - AccountID const& dst, - std::uint32_t seqProxyValue); +channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue); inline uint256 channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) @@ -103,17 +99,13 @@ signClaimAuth( STAmount const& authAmt); Rate -rate( - Env& env, - Account const& account, - Account const& dest, - std::uint32_t const& seq); +rate(Env& env, Account const& account, Account const& dest, std::uint32_t const& seq); } // namespace paychan } // namespace jtx } // namespace test -} // namespace ripple +} // namespace xrpl #endif diff --git a/src/xrpld/app/misc/EscrowUtils.h b/src/xrpld/app/misc/EscrowUtils.h deleted file mode 100644 index 8c80fff3761..00000000000 --- a/src/xrpld/app/misc/EscrowUtils.h +++ /dev/null @@ -1,580 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2025 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_APP_MISC_ESCROWUTILS_H_INLCUDED -#define RIPPLE_APP_MISC_ESCROWUTILS_H_INLCUDED - -#include -#include - -#include -#include -#include -#include - -namespace ripple { - -template -static NotTEC -createPreflightHelper(PreflightContext const& ctx); - -template <> -NotTEC -createPreflightHelper(PreflightContext const& ctx) -{ - STAmount const amount = ctx.tx[sfAmount]; - if (amount.native() || amount <= beast::zero) - return temBAD_AMOUNT; - - if (badCurrency() == amount.getCurrency()) - return temBAD_CURRENCY; - - return tesSUCCESS; -} - -template <> -NotTEC -createPreflightHelper(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureMPTokensV1)) - return temDISABLED; - - auto const amount = ctx.tx[sfAmount]; - if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || - amount <= beast::zero) - return temBAD_AMOUNT; - - return tesSUCCESS; -} - -template -static TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount); - -template <> -TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION - auto const sleIssuer = ctx.view.read(keylet::account(issuer)); - if (!sleIssuer) - return tecNO_ISSUER; - if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) - return tecNO_PERMISSION; - - // If the account does not have a trustline to the issuer, return tecNO_LINE - auto const sleRippleState = - ctx.view.read(keylet::line(account, issuer, amount.getCurrency())); - if (!sleRippleState) - return tecNO_LINE; - - STAmount const balance = (*sleRippleState)[sfBalance]; - - // If balance is positive, issuer must have higher address than account - if (balance > beast::zero && issuer < account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If balance is negative, issuer must have lower address than account - if (balance < beast::zero && issuer > account) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), account); - ter != tesSUCCESS) - return ter; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the account, return tecFROZEN - if (isFrozen(ctx.view, account, amount.issue())) - return tecFROZEN; - - // If the issuer has frozen the destination, return tecFROZEN - if (isFrozen(ctx.view, dest, amount.issue())) - return tecFROZEN; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.getCurrency(), - issuer, - fhIGNORE_FREEZE, - ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::zero) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - // If the amount is not addable to the balance, return tecPRECISION_LOSS - if (!canAdd(spendableAmount, amount)) - return tecPRECISION_LOSS; - - return tesSUCCESS; -} - -template <> -TER -createPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecNO_PERMISSION - if (issuer == account) - return tecNO_PERMISSION; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = - keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION - if (!sleIssuance->isFlag(lsfMPTCanEscrow)) - return tecNO_PERMISSION; - - // If the issuer is not the same as the issuer of the mpt, return - // tecNO_PERMISSION - if (sleIssuance->getAccountID(sfIssuer) != issuer) - return tecNO_PERMISSION; // LCOV_EXCL_LINE - - // If the account does not have the mpt, return tecOBJECT_NOT_FOUND - if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account))) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the account is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = - requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has requireAuth set, check if the destination is - // authorized - if (auto const ter = - requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the account, return tecLOCKED - if (isFrozen(ctx.view, account, mptIssue)) - return tecLOCKED; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - // If the mpt cannot be transferred, return tecNO_AUTH - if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); - ter != tesSUCCESS) - return ter; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.get(), - fhIGNORE_FREEZE, - ahIGNORE_AUTH, - ctx.j); - - // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS - if (spendableAmount <= beast::zero) - return tecINSUFFICIENT_FUNDS; - - // If the spendable amount is less than the amount, return - // tecINSUFFICIENT_FUNDS - if (spendableAmount < amount) - return tecINSUFFICIENT_FUNDS; - - return tesSUCCESS; -} - -template -static TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal); - -template <> -TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal) -{ - // Defensive: Issuer cannot create an escrow - // LCOV_EXCL_START - if (issuer == sender) - return tecINTERNAL; - // LCOV_EXCL_STOP - - auto const ter = rippleCredit( - view, - sender, - issuer, - amount, - amount.holds() ? false : true, - journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - return tesSUCCESS; -} - -template <> -TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal) -{ - // Defensive: Issuer cannot create an escrow - // LCOV_EXCL_START - if (issuer == sender) - return tecINTERNAL; - // LCOV_EXCL_STOP - - auto const ter = rippleLockEscrowMPT(view, sender, amount, journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - return tesSUCCESS; -} - -template -static TER -escrowUnlockPreclaimHelper( - ReadView const& view, - AccountID const& account, - STAmount const& amount, - bool checkFreeze = true); - -template <> -TER -escrowUnlockPreclaimHelper( - ReadView const& view, - AccountID const& account, - STAmount const& amount, - bool checkFreeze) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tesSUCCESS - if (issuer == account) - return tesSUCCESS; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(view, amount.issue(), account); - ter != tesSUCCESS) - return ter; - - // If the issuer has deep frozen the account, return tecFROZEN - if (checkFreeze && - isDeepFrozen(view, account, amount.getCurrency(), amount.getIssuer())) - return tecFROZEN; - - return tesSUCCESS; -} - -template <> -TER -escrowUnlockPreclaimHelper( - ReadView const& view, - AccountID const& account, - STAmount const& amount, - bool checkFreeze) -{ - AccountID issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tesSUCCESS - if (issuer == account) - return tesSUCCESS; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = - keylet::mptIssuance(amount.get().getMptID()); - auto const sleIssuance = view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the account is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = - requireAuth(view, mptIssue, account, AuthType::WeakAuth); - ter != tesSUCCESS) - return ter; - - // If the issuer has frozen the account, return tecLOCKED - if (checkFreeze && isFrozen(view, account, mptIssue)) - return tecLOCKED; - - return tesSUCCESS; -} - -template -static TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal); - -template <> -TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal) -{ - Keylet const trustLineKey = keylet::line(receiver, amount.issue()); - bool const recvLow = issuer > receiver; - bool const senderIssuer = issuer == sender; - bool const receiverIssuer = issuer == receiver; - bool const issuerHigh = issuer > receiver; - - // LCOV_EXCL_START - if (senderIssuer) - return tecINTERNAL; - // LCOV_EXCL_STOP - - if (receiverIssuer) - return tesSUCCESS; - - if (!view.exists(trustLineKey) && createAsset && !receiverIssuer) - { - // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - JLOG(journal.trace()) << "Trust line does not exist. " - "Insufficent reserve to create line."; - - return tecNO_LINE_INSUF_RESERVE; - } - - Currency const currency = amount.getCurrency(); - STAmount initialBalance(amount.issue()); - initialBalance.setIssuer(noAccount()); - - // clang-format off - if (TER const ter = trustCreate( - view, // payment sandbox - recvLow, // is dest low? - issuer, // source - receiver, // destination - trustLineKey.key, // ledger index - sleDest, // Account to add to - false, // authorize account - (sleDest->getFlags() & lsfDefaultRipple) == 0, - false, // freeze trust line - false, // deep freeze trust line - initialBalance, // zero initial balance - Issue(currency, receiver), // limit of zero - 0, // quality in - 0, // quality out - journal); // journal - !isTesSuccess(ter)) - { - return ter; // LCOV_EXCL_LINE - } - // clang-format on - - view.update(sleDest); - } - - if (!view.exists(trustLineKey) && !receiverIssuer) - return tecNO_LINE; - - auto const xferRate = transferRate(view, amount); - // update if issuer rate is less than locked rate - if (xferRate < lockedRate) - lockedRate = xferRate; - - // Transfer Rate only applies when: - // 1. Issuer is not involved in the transfer (senderIssuer or - // receiverIssuer) - // 2. The locked rate is different from the parity rate - - // NOTE: Transfer fee in escrow works a bit differently from a normal - // payment. In escrow, the fee is deducted from the locked/sending amount, - // whereas in a normal payment, the transfer fee is taken on top of the - // sending amount. - auto finalAmt = amount; - if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) - { - // compute transfer fee, if any - auto const xferFee = amount.value() - - divideRound(amount, lockedRate, amount.issue(), true); - // compute balance to transfer - finalAmt = amount.value() - xferFee; - } - - // validate the line limit if the account submitting txn is not the receiver - // of the funds - if (!createAsset) - { - auto const sleRippleState = view.peek(trustLineKey); - if (!sleRippleState) - return tecINTERNAL; // LCOV_EXCL_LINE - - // if the issuer is the high, then we use the low limit - // otherwise we use the high limit - STAmount const lineLimit = sleRippleState->getFieldAmount( - issuerHigh ? sfLowLimit : sfHighLimit); - - STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance); - - // flip the sign of the line balance if the issuer is not high - if (!issuerHigh) - lineBalance.negate(); - - // add the final amount to the line balance - lineBalance += finalAmt; - - // if the transfer would exceed the line limit return tecLIMIT_EXCEEDED - if (lineLimit < lineBalance) - return tecLIMIT_EXCEEDED; - } - - // if destination is not the issuer then transfer funds - if (!receiverIssuer) - { - auto const ter = - rippleCredit(view, issuer, receiver, finalAmt, true, journal); - if (ter != tesSUCCESS) - return ter; // LCOV_EXCL_LINE - } - return tesSUCCESS; -} - -template <> -TER -escrowUnlockApplyHelper( - ApplyView& view, - Rate lockedRate, - std::shared_ptr const& sleDest, - STAmount const& xrpBalance, - STAmount const& amount, - AccountID const& issuer, - AccountID const& sender, - AccountID const& receiver, - bool createAsset, - beast::Journal journal) -{ - bool const senderIssuer = issuer == sender; - bool const receiverIssuer = issuer == receiver; - - auto const mptID = amount.get().getMptID(); - auto const issuanceKey = keylet::mptIssuance(mptID); - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && - createAsset && !receiverIssuer) - { - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - return tecINSUFFICIENT_RESERVE; - } - - if (auto const ter = - MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); - !isTesSuccess(ter)) - { - return ter; // LCOV_EXCL_LINE - } - - // update owner count. - adjustOwnerCount(view, sleDest, 1, journal); - } - - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && - !receiverIssuer) - return tecNO_PERMISSION; - - auto const xferRate = transferRate(view, amount); - // update if issuer rate is less than locked rate - if (xferRate < lockedRate) - lockedRate = xferRate; - - // Transfer Rate only applies when: - // 1. Issuer is not involved in the transfer (senderIssuer or - // receiverIssuer) - // 2. The locked rate is different from the parity rate - - // NOTE: Transfer fee in escrow works a bit differently from a normal - // payment. In escrow, the fee is deducted from the locked/sending amount, - // whereas in a normal payment, the transfer fee is taken on top of the - // sending amount. - auto finalAmt = amount; - if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) - { - // compute transfer fee, if any - auto const xferFee = amount.value() - - divideRound(amount, lockedRate, amount.asset(), true); - // compute balance to transfer - finalAmt = amount.value() - xferFee; - } - - return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal); -} - -} // namespace ripple - -#endif // RIPPLE_APP_MISC_ESCROWUTILS_H_INLCUDED diff --git a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp index e2017aa0767..a8b3973df82 100644 --- a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp @@ -148,8 +148,7 @@ doGatewayBalances(RPC::JsonContext& context) if (sle->getType() == ltESCROW) { auto const& amount = sle->getFieldAmount(sfAmount); - // Gateway Balance should not include MPTs - if (amount.holds()) + if (amount.native() || amount.holds()) return; auto& bal = locked[amount.get().currency]; @@ -179,6 +178,9 @@ doGatewayBalances(RPC::JsonContext& context) if (sle->getType() == ltPAYCHAN) { auto const& amount = sle->getFieldAmount(sfAmount); + if (amount.native() || amount.holds()) + return; + auto const& balance = sle->getFieldAmount(sfBalance); auto const& netAmount = amount - balance; auto& bal = locked[netAmount.get().currency]; From 61dc5be6c446b4ff5d7c72a8913401fb7310f36a Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 May 2026 18:09:57 +0200 Subject: [PATCH 3/3] fix --- include/xrpl/ledger/helpers/EscrowHelpers.h | 8 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 4 +- .../payment_channel/PaymentChannelFund.cpp | 19 + src/test/app/PayChanToken_test.cpp | 773 +++++++++++++++++- src/test/jtx/TestHelpers.h | 1 - src/test/jtx/impl/TestHelpers.cpp | 1 - 6 files changed, 795 insertions(+), 11 deletions(-) diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index fc2981b7399..e29a5116d71 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -165,6 +165,7 @@ escrowUnlockApplyHelper( bool const recvLow = issuer > receiver; bool const senderIssuer = issuer == sender; bool const receiverIssuer = issuer == receiver; + bool const lineExisted = view.exists(trustLineKey); if (senderIssuer) return tecINTERNAL; // LCOV_EXCL_LINE @@ -172,7 +173,7 @@ escrowUnlockApplyHelper( if (receiverIssuer) return tesSUCCESS; - if (!view.exists(trustLineKey) && createAsset) + if (!lineExisted && createAsset) { // Can the account cover the trust line's reserve? if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; @@ -239,9 +240,8 @@ escrowUnlockApplyHelper( finalAmt = amount.value() - xferFee; } - // validate the line limit if the account submitting txn is not the receiver - // of the funds - if (!createAsset) + // validate the line limit for pre-existing trust lines + if (lineExisted) { auto const sleRippleState = view.peek(trustLineKey); if (!sleRippleState) diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 838254ab649..369def41ec0 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -254,8 +254,8 @@ ValidMPTIssuance::finalize( if (tx.getTxnType() == ttPAYCHAN_CLAIM) { - if (mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && - mptokensDeleted_ == 0 && mptokensCreated_ <= 1) + if (mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensDeleted_ == 0 && + mptokensCreated_ <= 1) return true; } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index e4de92b6dfd..5cb0332e0f7 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -200,6 +200,25 @@ PaymentChannelFund::doApply() return tecNO_DST; } + if (!isXRP(amount)) + { + if (auto const ret = std::visit( + [&](T const&) -> TER { + auto const& iss = amount.get(); + if (isFrozen(ctx_.view(), account_, iss)) + { + if constexpr (std::is_same_v) + return tecFROZEN; + else + return tecLOCKED; + } + return TER{tesSUCCESS}; + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } + if (isXRP(amount)) (*sle)[sfBalance] = (*sle)[sfBalance] - amount; else diff --git a/src/test/app/PayChanToken_test.cpp b/src/test/app/PayChanToken_test.cpp index 32900c61127..2bd128094bb 100644 --- a/src/test/app/PayChanToken_test.cpp +++ b/src/test/app/PayChanToken_test.cpp @@ -19,10 +19,10 @@ #include -#include #include #include +#include #include #include #include @@ -594,9 +594,10 @@ struct PayChanToken_test : public beast::unit_test::Suite auto const bobPreLimit = env.limit(bob, USD); - // bob can claim even if bobs limit is too low + // bob cannot claim if bobs limit is too low auto const sig = paychan::signClaimAuth(alice.pk(), alice.sk(), chan, USD(5)); - env(paychan::claim(bob, chan, USD(5), USD(5), Slice(sig), alice.pk()), Ter(tesSUCCESS)); + env(paychan::claim(bob, chan, USD(5), USD(5), Slice(sig), alice.pk()), + Ter(tecLIMIT_EXCEEDED)); env.close(); // bobs limit is not changed @@ -2846,6 +2847,761 @@ struct PayChanToken_test : public beast::unit_test::Suite } } + void + testIOUClawbackInteraction(FeatureBitset features) + { + testcase("IOU Clawback Interaction"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // Attempt to shelter funds from clawback by locking in channel + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(4'000), settleDelay, pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == USD(1'000)); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(4'000)); + + env(claw(gw, alice["USD"](1'000))); + env.close(); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + + auto const chan = paychan::channel(alice, bob, seq1); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); + BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == USD(4'000)); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(4'000)); + + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + // Alice recovered 4000 USD that survived the clawback + BEAST_EXPECT(env.balance(alice, USD) == USD(4'000)); + BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0)); + } + + // Clawback from dest with active channel (claim after clawback) + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + env(claw(gw, bob["USD"](5'000))); + env.close(); + BEAST_EXPECT(env.balance(bob, USD) == USD(0)); + + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(bob, USD) == USD(1'000)); + } + } + + void + testIOUFundAfterFreeze(FeatureBitset features) + { + testcase("IOU Fund After Freeze"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // Fund channel after destination is frozen + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + env(trust(gw, USD(100'000), bob, tfSetFreeze)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + env(paychan::fund(alice, chan, USD(1'000)), Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == USD(2'000)); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(500)); + env(paychan::claim( + bob, chan, USD(500), USD(500), Slice(sig), pk), + Ter(tesSUCCESS)); + env.close(); + } + + // Fund channel after sender frozen + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + env(trust(gw, USD(100'000), alice, tfSetFreeze)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + env(paychan::fund(alice, chan, USD(1'000)), + Ter(tecFROZEN)); + env.close(); + BEAST_EXPECT( + paychan::channelAmount(*env.current(), chan) == USD(1'000)); + } + + // Close channel refund with deep frozen sender + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + auto const preAlice = env.balance(alice, USD); + + env(trust(gw, USD(100'000), alice, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == preAlice + USD(1'000)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); + } + } + + void + testIOUDeepFreezeAfterCreate(FeatureBitset features) + { + testcase("IOU Deep Freeze After Create"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // Create channel, deep freeze sender, bob claims (sender freeze + // doesn't block claim since funds already locked) + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + // Deep freeze alice's trust line + env(trust(gw, USD(100'000), alice, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // Bob claims - only dest freeze is checked, not sender + auto const chan = paychan::channel(alice, bob, seq1); + auto const preBob = env.balance(bob, USD); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(500)); + env(paychan::claim( + bob, chan, USD(500), USD(500), Slice(sig), pk), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(bob, USD) == preBob + USD(500)); + } + } + + void + testIOUMultiChannelDrain(FeatureBitset features) + { + testcase("IOU Multi Channel Drain"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, carol, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice, bob, carol); + env.close(); + env(pay(gw, alice, USD(5'000))); + env(pay(gw, bob, USD(5'000))); + env(pay(gw, carol, USD(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(3'000), settleDelay, pk)); + env.close(); + BEAST_EXPECT(env.balance(alice, USD) == USD(2'000)); + + auto const seq2 = env.seq(alice); + env(paychan::create(alice, carol, USD(2'000), settleDelay, pk)); + env.close(); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + + env(paychan::create(alice, bob, USD(1), settleDelay, pk), + Ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + auto const chan1 = paychan::channel(alice, bob, seq1); + auto sig = + paychan::signClaimAuth(pk, alice.sk(), chan1, USD(3'000)); + env(paychan::claim( + bob, chan1, USD(3'000), USD(3'000), Slice(sig), pk)); + env.close(); + BEAST_EXPECT(env.balance(bob, USD) == USD(8'000)); + + auto const chan2 = paychan::channel(alice, carol, seq2); + sig = paychan::signClaimAuth(pk, alice.sk(), chan2, USD(2'000)); + env(paychan::claim( + carol, chan2, USD(2'000), USD(2'000), Slice(sig), pk)); + env.close(); + BEAST_EXPECT(env.balance(carol, USD) == USD(7'000)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + } + } + + void + testIOUTransferRatePartialClaims(FeatureBitset features) + { + testcase("IOU Transfer Rate Partial Claims"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // Partial claim at high rate, rate drops, second claim at lower rate + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env(rate(gw, 1.25)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + auto const preBob = env.balance(bob, USD); + auto const chan = paychan::channel(alice, bob, seq1); + + auto sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(500)); + env(paychan::claim( + bob, chan, USD(500), USD(500), Slice(sig), pk)); + env.close(); + BEAST_EXPECT(env.balance(bob, USD) == preBob + USD(400)); + + env(rate(gw, 1.0)); + env.close(); + + sig = paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(bob, USD) == preBob + USD(900)); + } + + // Create at parity, issuer raises rate, claim uses locked parity + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + env(rate(gw, 2.0)); + env.close(); + + auto const preBob = env.balance(bob, USD); + auto const chan = paychan::channel(alice, bob, seq1); + + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(bob, USD) == preBob + USD(1'000)); + } + } + + void + testIOUTrustLineLimitClaim(FeatureBitset features) + { + testcase("IOU Trust Line Limit Claim"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + // Claim that would exceed bob's trust line limit + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env(trust(alice, USD(100'000))); + env(trust(bob, USD(500))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(200))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + + // Bob claims 250 for self — succeeds (200 + 250 = 450 < 500) + auto sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(250)); + env(paychan::claim( + bob, chan, USD(250), USD(250), Slice(sig), pk)); + env.close(); + BEAST_EXPECT(env.balance(bob, USD) == USD(450)); + + // Bob claims more — fails, would exceed limit (450 + 350 > 500) + sig = paychan::signClaimAuth(pk, alice.sk(), chan, USD(600)); + env(paychan::claim( + bob, chan, USD(600), USD(600), Slice(sig), pk), + Ter(tecLIMIT_EXCEEDED)); + env.close(); + BEAST_EXPECT(env.balance(bob, USD) == USD(450)); + + // Alice closing on bob's behalf also blocked by limit + env(paychan::claim(alice, chan, USD(600), USD(600)), + Txflags(tfClose), + Ter(tecLIMIT_EXCEEDED)); + env.close(); + } + } + + void + testIOUAllowLockingClearedClaim(FeatureBitset features) + { + testcase("IOU Allow Locking Cleared Claim"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + { + Env env{*this, features}; + env.fund(XRP(10'000), alice, bob, gw); + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + env.trust(USD(100'000), alice); + env.trust(USD(100'000), bob); + env.close(); + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk)); + env.close(); + + env(fclear(gw, asfAllowTrustLineLocking)); + env.close(); + + auto const preBob = env.balance(bob, USD); + + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, USD(1'000)); + env(paychan::claim( + bob, chan, USD(1'000), USD(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(bob, USD) == preBob + USD(1'000)); + + env(paychan::create(alice, bob, USD(1'000), settleDelay, pk), + Ter(tecNO_PERMISSION)); + env.close(); + } + } + + void + testMPTClawbackInteraction(FeatureBitset features) + { + testcase("MPT Clawback Interaction"); + using namespace test::jtx; + using namespace std::literals; + + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanClawback}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(5'000))); + env(pay(gw, bob, MPT(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, MPT(4'000), settleDelay, pk)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == MPT(1'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 4'000); + + mptGw.claw(gw, alice, 1'000); + BEAST_EXPECT(env.balance(alice, MPT) == MPT(0)); + + auto const chan = paychan::channel(alice, bob, seq1); + BEAST_EXPECT(paychan::channelExists(*env.current(), chan)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 4'000); + + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == MPT(4'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + } + } + + void + testMPTClaimAutoCreate(FeatureBitset features) + { + testcase("MPT Claim Auto Create"); + using namespace test::jtx; + using namespace std::literals; + + // Claim auto-creates MPToken for receiver without authorize + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), bob); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), bob))); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(500)); + env(paychan::claim( + bob, chan, MPT(500), MPT(500), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.le(keylet::mptoken(MPT.mpt(), bob))); + BEAST_EXPECT(env.balance(bob, MPT) == MPT(500)); + } + + // requireAuth blocks claim even with auto-create + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + env.fund(XRP(10'000), bob); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | + tfMPTRequireAuth}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = gw, .holder = alice}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + Ter(tecNO_AUTH)); + env.close(); + } + } + + void + testMPTFreezeClaimClose(FeatureBitset features) + { + testcase("MPT Freeze Claim Close"); + using namespace test::jtx; + using namespace std::literals; + + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk)); + env.close(); + + auto const preAlice = env.balance(alice, MPT); + + mptGw.set({.account = gw, .holder = alice, .flags = tfMPTLock}); + + auto const chan = paychan::channel(alice, bob, seq1); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(500)); + + env(paychan::claim( + bob, chan, MPT(500), MPT(500), Slice(sig), pk), + Ter(tesSUCCESS)); + env.close(); + + mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock}); + + auto const sig2 = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim( + bob, chan, MPT(1'000), MPT(1'000), Slice(sig2), pk), + Ter(tecLOCKED)); + env.close(); + + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAlice + MPT(500)); + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); + } + } + + void + testMPTCanEscrowRequired(FeatureBitset features) + { + testcase("MPT CanEscrow Required"); + using namespace test::jtx; + using namespace std::literals; + + // Without canEscrow flag, channel creation fails + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk), + Ter(tecNO_PERMISSION)); + env.close(); + } + + // With canEscrow flag, channel works and claim succeeds + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(5'000))); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const seq1 = env.seq(alice); + env(paychan::create(alice, bob, MPT(1'000), settleDelay, pk)); + env.close(); + + auto const chan = paychan::channel(alice, bob, seq1); + auto const preBob = env.balance(bob, MPT); + auto const sig = + paychan::signClaimAuth(pk, alice.sk(), chan, MPT(1'000)); + env(paychan::claim( + bob, chan, MPT(1'000), MPT(1'000), Slice(sig), pk)); + env.close(); + + BEAST_EXPECT(env.balance(bob, MPT) == preBob + MPT(1'000)); + } + } + void testIOUWithFeats(FeatureBitset features) { @@ -2866,6 +3622,13 @@ struct PayChanToken_test : public beast::unit_test::Suite testIOUFreeze(features); testIOUINSF(features); testIOUPrecisionLoss(features); + testIOUClawbackInteraction(features); + testIOUFundAfterFreeze(features); + testIOUDeepFreezeAfterCreate(features); + testIOUMultiChannelDrain(features); + testIOUTransferRatePartialClaims(features); + testIOUTrustLineLimitClaim(features); + testIOUAllowLockingClearedClaim(features); } void @@ -2885,6 +3648,10 @@ struct PayChanToken_test : public beast::unit_test::Suite testMPTLock(features); testMPTCanTransfer(features); testMPTDestroy(features); + testMPTClawbackInteraction(features); + testMPTClaimAutoCreate(features); + testMPTFreezeClaimClose(features); + testMPTCanEscrowRequired(features); } public: diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 101d774fefe..6a23fdfd8cd 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -567,7 +567,6 @@ accountBalance(Env& env, Account const& acct); [[nodiscard]] bool expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedValue); - /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index c9a3543e21d..48576422849 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -529,7 +529,6 @@ expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedVal return accountBalance(env, acct) == to_string(expectedValue.xrp()); } - /* Crossing Limits */ /******************************************************************************/