mirror of
https://github.com/Xahau/xahaud.git
synced 2026-09-16 12:38:31 +00:00
Compare commits
2 Commits
HookFeeV2
...
recipient-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30a065e726 | ||
|
|
bf339822c1 |
@@ -37,6 +37,8 @@ enum ltRIPPLE_STATE {
|
||||
lsfHighFreeze = 0x00800000,
|
||||
lsfLowDeepFreeze = 0x02000000,
|
||||
lsfHighDeepFreeze = 0x04000000,
|
||||
lsfLowPersist = 0x08000000,
|
||||
lsfHighPersist = 0x10000000,
|
||||
lsfAMMNode = 0x01000000,
|
||||
};
|
||||
enum ltSIGNER_LIST {
|
||||
|
||||
@@ -55,7 +55,9 @@ enum TrustSetFlags : uint32_t {
|
||||
tfSetFreeze = 0x00100000,
|
||||
tfClearFreeze = 0x00200000,
|
||||
tfSetDeepFreeze = 0x00400000,
|
||||
tfClearDeepFreeze = 0x00800000
|
||||
tfClearDeepFreeze = 0x00800000,
|
||||
tfSetPersist = 0x01000000,
|
||||
tfClearPersist = 0x02000000
|
||||
};
|
||||
|
||||
enum EnableAmendmentFlags : uint32_t {
|
||||
|
||||
@@ -166,6 +166,8 @@ enum LedgerSpecificFlags {
|
||||
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
|
||||
lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag
|
||||
lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag
|
||||
lsfLowPersist = 0x08000000, // True, low side keeps the line at zero
|
||||
lsfHighPersist = 0x10000000, // True, high side keeps the line at zero
|
||||
lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client
|
||||
// apps to identify payments via AMM.
|
||||
|
||||
|
||||
@@ -125,11 +125,14 @@ enum TrustSetFlags : uint32_t {
|
||||
tfSetFreeze = 0x00100000,
|
||||
tfClearFreeze = 0x00200000,
|
||||
tfSetDeepFreeze = 0x00400000,
|
||||
tfClearDeepFreeze = 0x00800000
|
||||
tfClearDeepFreeze = 0x00800000,
|
||||
tfSetPersist = 0x01000000,
|
||||
tfClearPersist = 0x02000000
|
||||
};
|
||||
constexpr std::uint32_t tfTrustSetMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze | tfSetPersist |
|
||||
tfClearPersist);
|
||||
|
||||
// EnableAmendment flags:
|
||||
enum EnableAmendmentFlags : uint32_t {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(NoRecipientLimit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -529,7 +529,12 @@ class Check_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(check::create(gw1, alice, USD(50)), ter(tecFROZEN));
|
||||
env.close();
|
||||
env(pay(gw1, alice, USD(1)), ter(tecPATH_DRY));
|
||||
// A pure issue cannot be frozen; what refused this payment was
|
||||
// alice's limit of zero, which no longer caps her issuer under
|
||||
// NoRecipientLimit.
|
||||
env(pay(gw1, alice, USD(1)),
|
||||
ter(features[featureNoRecipientLimit] ? TER(tesSUCCESS)
|
||||
: TER(tecPATH_DRY)));
|
||||
env.close();
|
||||
|
||||
// Clear that freeze.
|
||||
@@ -736,11 +741,13 @@ class Check_test : public beast::unit_test::suite
|
||||
// bob sets up the trust line, but not at a high enough limit.
|
||||
env(trust(bob, USD(9.5)));
|
||||
env.close();
|
||||
if (!cashCheckMakesTrustLine)
|
||||
if (!cashCheckMakesTrustLine && !features[featureNoRecipientLimit])
|
||||
{
|
||||
// If cashing a check is allowed to exceed the trust line
|
||||
// limit then this returns tesSUCCESS and the check is
|
||||
// removed from the ledger which would mess up later tests.
|
||||
// limit (CheckCashMakesTrustLine, or NoRecipientLimit
|
||||
// exempting bob from his limit on his issuer) then this
|
||||
// returns tesSUCCESS and the check is removed from the
|
||||
// ledger which would mess up later tests.
|
||||
env(check::cash(bob, chkId1, USD(10)), ter(tecPATH_PARTIAL));
|
||||
env.close();
|
||||
}
|
||||
@@ -829,9 +836,22 @@ class Check_test : public beast::unit_test::suite
|
||||
// a payment to bob cannot exceed that trust line, but cashing
|
||||
// a check can.
|
||||
|
||||
// Payment of 20 USD fails.
|
||||
env(pay(gw, bob, USD(20)), ter(tecPATH_PARTIAL));
|
||||
env.close();
|
||||
// Payment of 20 USD fails, unless recipients are exempt
|
||||
// from their limit, in which case it succeeds and is undone
|
||||
// so the check below still tells the same story.
|
||||
if (features[featureNoRecipientLimit])
|
||||
{
|
||||
env(pay(gw, bob, USD(20)));
|
||||
env.close();
|
||||
env.require(balance(bob, USD(30)));
|
||||
env(pay(bob, gw, USD(20)));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
env(pay(gw, bob, USD(20)), ter(tecPATH_PARTIAL));
|
||||
env.close();
|
||||
}
|
||||
|
||||
uint256 const chkId20{getCheckIndex(gw, env.seq(gw))};
|
||||
env(check::create(gw, bob, USD(20)));
|
||||
@@ -977,7 +997,9 @@ class Check_test : public beast::unit_test::suite
|
||||
|
||||
// bob tries to cash the check again but fails because his trust
|
||||
// limit is too low.
|
||||
if (!cashCheckMakesTrustLine)
|
||||
bool const exceedsLimit =
|
||||
cashCheckMakesTrustLine || features[featureNoRecipientLimit];
|
||||
if (!exceedsLimit)
|
||||
{
|
||||
// If cashing a check is allowed to exceed the trust line
|
||||
// limit then this returns tesSUCCESS and the check is
|
||||
@@ -994,7 +1016,7 @@ class Check_test : public beast::unit_test::suite
|
||||
// o If it can build a trust line, then the check is allowed to
|
||||
// exceed the trust limit and bob gets the full transfer.
|
||||
env(check::cash(bob, chkId, check::DeliverMin(USD(4))));
|
||||
STAmount const bobGot = cashCheckMakesTrustLine ? USD(7) : USD(5);
|
||||
STAmount const bobGot = exceedsLimit ? USD(7) : USD(5);
|
||||
verifyDeliveredAmount(env, bobGot);
|
||||
env.require(balance(alice, USD(8) - bobGot));
|
||||
env.require(balance(bob, bobGot));
|
||||
@@ -2690,8 +2712,10 @@ class Check_test : public beast::unit_test::suite
|
||||
testCreateValid(features);
|
||||
testCreateDisallowIncoming(features);
|
||||
testCreateInvalid(features);
|
||||
testCreateInvalid(features - featureNoRecipientLimit);
|
||||
testCashXRP(features);
|
||||
testCashIOU(features);
|
||||
testCashIOU(features - featureNoRecipientLimit);
|
||||
testCashXferFee(features);
|
||||
testCashQuality(features);
|
||||
testCashInvalid(features);
|
||||
|
||||
@@ -960,12 +960,16 @@ struct Flow_test : public beast::unit_test::suite
|
||||
env.require(balance(alice, EUR(600)));
|
||||
aliceOffers = offersOnAccount(env, alice);
|
||||
BEAST_EXPECT(aliceOffers.size() == 1);
|
||||
// alice's EUR limit is 606 and she holds 600: the last step, gw2
|
||||
// issuing to alice, is capped at 6 EUR unless recipients are exempt
|
||||
// from their limit, in which case all 60 EUR cross.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
for (auto const& offerPtr : aliceOffers)
|
||||
{
|
||||
auto const offer = *offerPtr;
|
||||
BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
|
||||
BEAST_EXPECT(offer[sfTakerGets] == EUR(594));
|
||||
BEAST_EXPECT(offer[sfTakerPays] == USD(495));
|
||||
BEAST_EXPECT(offer[sfTakerGets] == (exempt ? EUR(540) : EUR(594)));
|
||||
BEAST_EXPECT(offer[sfTakerPays] == (exempt ? USD(450) : USD(495)));
|
||||
}
|
||||
}
|
||||
void
|
||||
@@ -1400,6 +1404,173 @@ struct Flow_test : public beast::unit_test::suite
|
||||
env.require(balance(alice, XRP(9000) - drops(20)));
|
||||
}
|
||||
|
||||
void
|
||||
testRecipientLimit(FeatureBitset features)
|
||||
{
|
||||
// A trust line limit governs an account used as an intermediary, not
|
||||
// an account receiving its issuer's token: with
|
||||
// featureNoRecipientLimit the issuer's step into the destination
|
||||
// ignores the destination's limit. Without it, today's behaviour
|
||||
// holds. Funds caps, intermediary caps and a non-issuer rippling into
|
||||
// the destination are unchanged in both regimes.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
testcase(
|
||||
exempt ? "Recipient limit (exempt)" : "Recipient limit (enforced)");
|
||||
|
||||
using namespace jtx;
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const dan = Account("dan");
|
||||
|
||||
{
|
||||
// Issuer pays a holder more than the holder's limit.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
env.trust(USD(100), alice);
|
||||
env(pay(gw, alice, USD(150)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
env.require(balance(alice, exempt ? USD(150) : USD(0)));
|
||||
}
|
||||
{
|
||||
// A line already at its limit: the dry check in the direct step.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
env.trust(USD(100), alice);
|
||||
env(pay(gw, alice, USD(100)));
|
||||
env(pay(gw, alice, USD(1)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_DRY)));
|
||||
env.require(balance(alice, exempt ? USD(101) : USD(100)));
|
||||
}
|
||||
{
|
||||
// Holder pays holder through the issuer; the recipient's limit is
|
||||
// the last step's limit.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
env.trust(USD(1000), alice);
|
||||
env.trust(USD(100), bob);
|
||||
env(pay(gw, alice, USD(500)));
|
||||
env(pay(alice, bob, USD(150)),
|
||||
paths(USD),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
env.require(balance(bob, exempt ? USD(150) : USD(0)));
|
||||
}
|
||||
{
|
||||
// Cross-currency: the last direct step follows a book step.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice, bob, carol);
|
||||
env.trust(USD(1000), carol);
|
||||
env.trust(USD(100), bob);
|
||||
env(pay(gw, carol, USD(500)));
|
||||
env(offer(carol, XRP(150), USD(150)));
|
||||
env(pay(alice, bob, USD(150)),
|
||||
sendmax(XRP(150)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
env.require(balance(bob, exempt ? USD(150) : USD(0)));
|
||||
}
|
||||
{
|
||||
// A partial payment delivers up to the limit today and all of it
|
||||
// when the recipient is exempt.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
env.trust(USD(1000), alice);
|
||||
env.trust(USD(100), bob);
|
||||
env(pay(gw, alice, USD(500)));
|
||||
env(pay(alice, bob, USD(150)),
|
||||
paths(USD),
|
||||
txflags(tfPartialPayment));
|
||||
env.require(balance(bob, exempt ? USD(150) : USD(100)));
|
||||
}
|
||||
{
|
||||
// Rippling through an intermediary is still capped by the
|
||||
// intermediary's limit in both regimes: bob's limit on alice's
|
||||
// USD is 10, and bob is not the destination.
|
||||
auto const USDA = alice["USD"];
|
||||
auto const USDB = bob["USD"];
|
||||
auto const USDC = carol["USD"];
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), alice, bob, carol, dan);
|
||||
env.trust(USDA(10), bob);
|
||||
env.trust(USDB(1000), carol);
|
||||
env.trust(USDC(1000), dan);
|
||||
env(pay(alice, dan, USDC(15)), paths(USDA), ter(tecPATH_PARTIAL));
|
||||
env.require(balance(dan, USDC(0)));
|
||||
env(pay(alice, dan, USDC(10)), paths(USDA));
|
||||
env.require(
|
||||
balance(bob, USDA(10)),
|
||||
balance(carol, USDB(10)),
|
||||
balance(dan, USDC(10)));
|
||||
}
|
||||
{
|
||||
// A non-issuer crediting the destination is still capped by the
|
||||
// destination's limit on that account in both regimes: the
|
||||
// payment names carol's own USD, so the engine does not route
|
||||
// through an issuer, and the last hop is bob issuing bob's USD to
|
||||
// carol, whose limit on bob is 10.
|
||||
auto const USDA = alice["USD"];
|
||||
auto const USDB = bob["USD"];
|
||||
auto const USDC = carol["USD"];
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), alice, bob, carol);
|
||||
env.trust(USDA(100), bob);
|
||||
env.trust(USDB(10), carol);
|
||||
env(pay(alice, carol, USDC(15)), path(bob), ter(tecPATH_PARTIAL));
|
||||
env.require(balance(carol, USDB(0)));
|
||||
env(pay(alice, carol, USDC(10)), path(bob));
|
||||
env.require(balance(bob, USDA(10)), balance(carol, USDB(10)));
|
||||
}
|
||||
{
|
||||
// The destination can be an intermediary earlier in the same
|
||||
// strand, in another currency: gw pays bob EUR through bob's own
|
||||
// USD line and mike's EUR/bobUSD offer. The USD hop into bob is
|
||||
// not the issuer delivering the named token, so bob's USD limit
|
||||
// (10) still caps it in both regimes.
|
||||
auto const EUR = gw["EUR"];
|
||||
auto const mike = Account("mike");
|
||||
auto const USDB = bob["USD"];
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, bob, mike);
|
||||
env(fset(bob, asfDefaultRipple));
|
||||
env.close();
|
||||
env.trust(USD(10), bob);
|
||||
env.trust(EUR(100), bob);
|
||||
env.trust(USDB(100), mike);
|
||||
env.trust(EUR(100), mike);
|
||||
env(pay(gw, mike, EUR(50)));
|
||||
env(offer(mike, USDB(15), EUR(15)));
|
||||
env.close();
|
||||
env(pay(gw, bob, EUR(15)),
|
||||
sendmax(USD(15)),
|
||||
path(bob, ~EUR),
|
||||
txflags(tfNoRippleDirect),
|
||||
ter(tecPATH_PARTIAL));
|
||||
env.require(balance(bob, USD(0)), balance(bob, EUR(0)));
|
||||
env(pay(gw, bob, EUR(10)),
|
||||
sendmax(USD(10)),
|
||||
path(bob, ~EUR),
|
||||
txflags(tfNoRippleDirect));
|
||||
env.require(
|
||||
balance(bob, USD(10)),
|
||||
balance(bob, EUR(10)),
|
||||
balance(mike, USDB(10)));
|
||||
}
|
||||
{
|
||||
// Redeeming to the issuer is capped by the sender's balance, not
|
||||
// by any limit, in both regimes. Once the balance is spent the
|
||||
// strand would have the sender issue its own USD to the issuer;
|
||||
// the sender is not the issuer named by the payment, so the
|
||||
// issuer's zero limit still refuses that.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
env.trust(USD(1000), alice);
|
||||
env(pay(gw, alice, USD(50)));
|
||||
env(pay(alice, gw, USD(60)), ter(tecPATH_PARTIAL));
|
||||
env.require(balance(alice, USD(50)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -1417,12 +1588,15 @@ struct Flow_test : public beast::unit_test::suite
|
||||
testTransferRate(features | ownerPaysFee);
|
||||
testSelfPayment1(features);
|
||||
testSelfPayment2(features);
|
||||
testSelfPayment2(features - featureNoRecipientLimit);
|
||||
testSelfFundedXRPEndpoint(false, features);
|
||||
testSelfFundedXRPEndpoint(true, features);
|
||||
testUnfundedOffer(features);
|
||||
testReexecuteDirectStep(features);
|
||||
testSelfPayLowQualityOffer(features);
|
||||
testTicketPay(features);
|
||||
testRecipientLimit(features);
|
||||
testRecipientLimit(features - featureNoRecipientLimit);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -80,17 +80,27 @@ class Path_test : public beast::unit_test::suite
|
||||
{
|
||||
jtx::Env
|
||||
pathTestEnv()
|
||||
{
|
||||
using namespace jtx;
|
||||
return pathTestEnv(supported_amendments());
|
||||
}
|
||||
|
||||
jtx::Env
|
||||
pathTestEnv(FeatureBitset features)
|
||||
{
|
||||
// These tests were originally written with search parameters that are
|
||||
// different from the current defaults. This function creates an env
|
||||
// with the search parameters that the tests were written for.
|
||||
using namespace jtx;
|
||||
return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->PATH_SEARCH_OLD = 7;
|
||||
cfg->PATH_SEARCH = 7;
|
||||
cfg->PATH_SEARCH_MAX = 10;
|
||||
return cfg;
|
||||
}));
|
||||
return Env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->PATH_SEARCH_OLD = 7;
|
||||
cfg->PATH_SEARCH = 7;
|
||||
cfg->PATH_SEARCH_MAX = 10;
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -734,11 +744,11 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
issues_path_negative_issue()
|
||||
issues_path_negative_issue(FeatureBitset features)
|
||||
{
|
||||
testcase("path negative: Issue #5");
|
||||
using namespace jtx;
|
||||
Env env = pathTestEnv();
|
||||
Env env = pathTestEnv(features);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
|
||||
env.trust(Account("bob")["USD"](100), "alice", "carol", "dan");
|
||||
env.trust(Account("alice")["USD"](100), "dan");
|
||||
@@ -751,14 +761,19 @@ public:
|
||||
find_paths(env, "alice", "bob", Account("bob")["USD"](25));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
|
||||
env(pay("alice", "bob", Account("alice")["USD"](25)), ter(tecPATH_DRY));
|
||||
// alice issuing her own USD to bob, who has no limit on her, is
|
||||
// refused; with featureNoRecipientLimit bob's limit does not cap
|
||||
// his issuer and the payment succeeds.
|
||||
bool const exempt = env.enabled(featureNoRecipientLimit);
|
||||
env(pay("alice", "bob", Account("alice")["USD"](25)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_DRY)));
|
||||
|
||||
result = find_paths(env, "alice", "bob", Account("alice")["USD"](25));
|
||||
BEAST_EXPECT(std::get<0>(result).empty());
|
||||
|
||||
env.require(balance("alice", Account("bob")["USD"](0)));
|
||||
env.require(balance("alice", Account("bob")["USD"](exempt ? -25 : 0)));
|
||||
env.require(balance("alice", Account("dan")["USD"](0)));
|
||||
env.require(balance("bob", Account("alice")["USD"](0)));
|
||||
env.require(balance("bob", Account("alice")["USD"](exempt ? 25 : 0)));
|
||||
env.require(balance("bob", Account("carol")["USD"](-75)));
|
||||
env.require(balance("bob", Account("dan")["USD"](0)));
|
||||
env.require(balance("carol", Account("bob")["USD"](75)));
|
||||
@@ -1518,7 +1533,9 @@ public:
|
||||
alternative_paths_consume_best_transfer();
|
||||
alternative_paths_consume_best_transfer_first();
|
||||
alternative_paths_limit_returned_paths_to_best_quality();
|
||||
issues_path_negative_issue();
|
||||
issues_path_negative_issue(jtx::supported_amendments());
|
||||
issues_path_negative_issue(
|
||||
jtx::supported_amendments() - featureNoRecipientLimit);
|
||||
issues_path_negative_ripple_client_issue_23_smaller();
|
||||
issues_path_negative_ripple_client_issue_23_larger();
|
||||
via_offers_via_gateway();
|
||||
|
||||
@@ -4959,9 +4959,12 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(
|
||||
postLocked ==
|
||||
(t.negative ? (preLocked + delta) : (preLocked - delta)));
|
||||
// src claim fails because trust limit is 0
|
||||
// src claim fails because trust limit is 0, unless a limit no
|
||||
// longer governs the receiver
|
||||
auto const testResult =
|
||||
t.hasTrustline ? ter(tesSUCCESS) : ter(tecPATH_DRY);
|
||||
(t.hasTrustline || features[featureNoRecipientLimit])
|
||||
? ter(tesSUCCESS)
|
||||
: ter(tecPATH_DRY);
|
||||
env(paychan::claim(t.src, chan, authAmt, authAmt), testResult);
|
||||
}
|
||||
}
|
||||
@@ -5288,11 +5291,25 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
assert(reqBal <= chanAmt);
|
||||
auto const preLocked = -lockedAmount(env, alice, gw, USD);
|
||||
BEAST_EXPECT(preLocked == USD(1000));
|
||||
// alice cannot claim because bobs amount would be > than limit
|
||||
env(paychan::claim(alice, chan, reqBal, authAmt), ter(tecPATH_DRY));
|
||||
auto const preBobLimit = limitAmount(env, bob, gw, USD);
|
||||
if (features[featureNoRecipientLimit])
|
||||
{
|
||||
// bob's limit does not govern bob receiving, whoever
|
||||
// finishes the claim
|
||||
env(paychan::claim(alice, chan, reqBal, authAmt));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(1000) + delta);
|
||||
reqBal = reqBal + delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// alice cannot claim because bobs amount would be > than
|
||||
// limit
|
||||
env(paychan::claim(alice, chan, reqBal, authAmt),
|
||||
ter(tecPATH_DRY));
|
||||
}
|
||||
|
||||
// bob can claim, increasing the limit amount
|
||||
auto const preBobLimit = limitAmount(env, bob, gw, USD);
|
||||
auto const sig =
|
||||
signClaimIOUAuth(alice.pk(), alice.sk(), chan, authAmt);
|
||||
env(paychan::claim(
|
||||
@@ -5927,9 +5944,11 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
testIOUUsingTickets(features);
|
||||
testIOUAutoTL(features);
|
||||
testIOURippleState(features);
|
||||
testIOURippleState(features - featureNoRecipientLimit);
|
||||
testIOUGateway(features);
|
||||
testIOULockedRate(features);
|
||||
testIOUTLLimitAmount(features);
|
||||
testIOUTLLimitAmount(features - featureNoRecipientLimit);
|
||||
testIOUTLRequireAuth(features);
|
||||
testIOUTLFreeze(features);
|
||||
testIOUTLINSF(features);
|
||||
|
||||
@@ -2924,6 +2924,78 @@ struct Remit_test : public beast::unit_test::suite
|
||||
ter(tecNO_PERMISSION));
|
||||
}
|
||||
|
||||
void
|
||||
testPersistLine(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
bool const persisted = features[featureNoRecipientLimit];
|
||||
testcase(
|
||||
std::string("remit-created line ") +
|
||||
(persisted ? "persists" : "is deleted") + " at zero balance");
|
||||
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(1000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), alice);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const lineKey = keylet::line(bob, gw, USD.currency);
|
||||
auto const persistFlag =
|
||||
bob.id() > gw.id() ? lsfHighPersist : lsfLowPersist;
|
||||
auto const persists = [&]() {
|
||||
auto const sle = env.le(lineKey);
|
||||
return sle && ((*sle)[sfFlags] & persistFlag);
|
||||
};
|
||||
|
||||
// The remit creates bob's line and, under the amendment, marks
|
||||
// bob's side as persisting.
|
||||
env(remit::remit(alice, bob), remit::amts({USD(1)}));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(lineKey));
|
||||
BEAST_EXPECT(persists() == persisted);
|
||||
BEAST_EXPECT(env.ownerCount(bob) == 1);
|
||||
|
||||
// A second remit onto the existing line changes nothing about it.
|
||||
env(remit::remit(alice, bob), remit::amts({USD(1)}));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists() == persisted);
|
||||
BEAST_EXPECT(env.ownerCount(bob) == 1);
|
||||
BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(2));
|
||||
|
||||
// bob spends the whole balance back to the issuer. Without the flag
|
||||
// the default line is deleted with it.
|
||||
env(pay(bob, gw, USD(2)));
|
||||
env.close();
|
||||
BEAST_EXPECT(bool(env.le(lineKey)) == persisted);
|
||||
BEAST_EXPECT(env.ownerCount(bob) == (persisted ? 1 : 0));
|
||||
|
||||
if (!persisted)
|
||||
return;
|
||||
|
||||
// bob can still receive on the kept line, and can clear the flag
|
||||
// to let it go once it is empty.
|
||||
env(remit::remit(alice, bob), remit::amts({USD(3)}));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(3));
|
||||
BEAST_EXPECT(env.ownerCount(bob) == 1);
|
||||
|
||||
env(trust(bob, USD(0), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(lineKey) && !persists());
|
||||
|
||||
env(pay(bob, gw, USD(3)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(lineKey));
|
||||
BEAST_EXPECT(env.ownerCount(bob) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -2946,6 +3018,8 @@ struct Remit_test : public beast::unit_test::suite
|
||||
testURIToken(features);
|
||||
testOptionals(features);
|
||||
testDestAMM(features);
|
||||
testPersistLine(features);
|
||||
testPersistLine(features - featureNoRecipientLimit);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -342,14 +342,17 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testExceedTrustLineLimit()
|
||||
testExceedTrustLineLimit(FeatureBitset features)
|
||||
{
|
||||
testcase(
|
||||
"Ensure that trust line limits are respected in payment "
|
||||
"transactions");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
// With featureNoRecipientLimit the issuer's payment into the
|
||||
// holder is not capped by the holder's limit.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
@@ -360,8 +363,10 @@ public:
|
||||
env.close();
|
||||
|
||||
// send a payment for a large quantity through the trust line
|
||||
env(pay(gw, alice, gw["USD"](200)), ter(tecPATH_PARTIAL));
|
||||
env(pay(gw, alice, gw["USD"](200)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
env.close();
|
||||
env.require(balance(alice, gw["USD"](exempt ? 200 : 0)));
|
||||
|
||||
// on the other hand, smaller payments should succeed
|
||||
env(pay(gw, alice, gw["USD"](20)));
|
||||
@@ -398,14 +403,17 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testTrustLineLimitsWithRippling()
|
||||
testTrustLineLimitsWithRippling(FeatureBitset features)
|
||||
{
|
||||
testcase(
|
||||
"Check that trust line limits are respected in conjunction "
|
||||
"with rippling feature");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
// With featureNoRecipientLimit bob, issuing his own USD to alice,
|
||||
// is not capped by alice's (zero) limit on him.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
|
||||
auto const bob = Account{"bob"};
|
||||
auto const alice = Account{"alice"};
|
||||
@@ -426,9 +434,12 @@ public:
|
||||
env.close();
|
||||
|
||||
// bob cannot place alice in his debt i.e. alice's balance of the USD
|
||||
// tokens cannot go below zero.
|
||||
env(pay(bob, alice, bob["USD"](11)), ter(tecPATH_PARTIAL));
|
||||
// tokens cannot go below zero, unless recipients are exempt from
|
||||
// their limit: then bob issues 1 USD of his own to alice.
|
||||
env(pay(bob, alice, bob["USD"](11)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
env.close();
|
||||
env.require(balance(bob, alice["USD"](exempt ? -1 : 10)));
|
||||
|
||||
// payments that respect the trust line limits of alice should succeed
|
||||
env(pay(bob, alice, bob["USD"](10)), ter(tesSUCCESS));
|
||||
@@ -618,6 +629,106 @@ public:
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testPersist(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
bool const enabled = features[featureNoRecipientLimit];
|
||||
testcase(
|
||||
std::string("Persist flag ") + (enabled ? "enabled" : "disabled"));
|
||||
|
||||
Env env{*this, features};
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
env.close();
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
env(trust(alice, USD(100), tfSetPersist), ter(temINVALID_FLAG));
|
||||
env(trust(alice, USD(100), tfClearPersist), ter(temINVALID_FLAG));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const lineKey = keylet::line(alice, gw, USD.currency);
|
||||
auto const persistFlag =
|
||||
alice.id() > gw.id() ? lsfHighPersist : lsfLowPersist;
|
||||
auto const persists = [&]() {
|
||||
auto const sle = env.le(lineKey);
|
||||
return sle && ((*sle)[sfFlags] & persistFlag);
|
||||
};
|
||||
|
||||
// Set and clear together is malformed.
|
||||
env(trust(alice, USD(100), tfSetPersist | tfClearPersist),
|
||||
ter(temINVALID_FLAG));
|
||||
|
||||
// Set on a fresh line: the line is created with the flag.
|
||||
env(trust(alice, USD(100), tfSetPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists());
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
|
||||
// Dropping the limit to zero leaves a persisting line in place and
|
||||
// alice keeps paying its reserve.
|
||||
env(trust(alice, USD(0)));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists());
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
|
||||
// Clearing the flag on an otherwise default line deletes it.
|
||||
env(trust(alice, USD(0), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(lineKey));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
|
||||
// Set alone, with a default limit, is not redundant: it creates the
|
||||
// line so a zero-limit holder can keep it.
|
||||
env(trust(alice, USD(0), tfSetPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists());
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
|
||||
// A balance that comes and goes does not delete a persisting line.
|
||||
env(pay(gw, alice, USD(10)));
|
||||
env(pay(alice, gw, USD(10)));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists());
|
||||
BEAST_EXPECT(env.balance(alice, USD.issue()) == USD(0));
|
||||
|
||||
// Clearing with no balance and no limit deletes the line.
|
||||
env(trust(alice, USD(0), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(lineKey));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
|
||||
// Clearing a line that never persisted is a no-op on the flag but
|
||||
// still a valid, non-redundant request when a limit is set.
|
||||
env(trust(alice, USD(50), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(lineKey) && !persists());
|
||||
|
||||
// Each side owns its own bit. With both set, clearing one leaves
|
||||
// the other side's claim, and its reserve, in place.
|
||||
auto const gwPersistFlag =
|
||||
persistFlag == lsfHighPersist ? lsfLowPersist : lsfHighPersist;
|
||||
env(trust(alice, USD(0), tfSetPersist));
|
||||
env(trust(gw, alice["USD"](0), tfSetPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(persists());
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
env(trust(alice, USD(0), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(lineKey) && !persists());
|
||||
BEAST_EXPECT((*env.le(lineKey))[sfFlags] & gwPersistFlag);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
env(trust(gw, alice["USD"](0), tfClearPersist));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(lineKey));
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -636,9 +747,13 @@ public:
|
||||
testDisallowIncoming(features);
|
||||
testTrustLineResetWithAuthFlag();
|
||||
testTrustLineDelete();
|
||||
testExceedTrustLineLimit();
|
||||
testExceedTrustLineLimit(features);
|
||||
testExceedTrustLineLimit(features - featureNoRecipientLimit);
|
||||
testAuthFlagTrustLines();
|
||||
testTrustLineLimitsWithRippling();
|
||||
testTrustLineLimitsWithRippling(features);
|
||||
testTrustLineLimitsWithRippling(features - featureNoRecipientLimit);
|
||||
testPersist(features);
|
||||
testPersist(features - featureNoRecipientLimit);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -176,9 +176,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
|
||||
env(pay(bob, alice, bob["USD"](1300)));
|
||||
env.require(balance(bob, alice["USD"](-600)));
|
||||
|
||||
// bob sends past limit
|
||||
env(pay(bob, alice, bob["USD"](1)), ter(tecPATH_DRY));
|
||||
env.require(balance(bob, alice["USD"](-600)));
|
||||
// bob sends past limit; with featureNoRecipientLimit bob is the
|
||||
// issuer of what alice receives and her limit does not cap him
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
env(pay(bob, alice, bob["USD"](1)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_DRY)));
|
||||
env.require(balance(bob, alice["USD"](exempt ? -601 : -600)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -467,6 +470,7 @@ public:
|
||||
auto testWithFeatures = [this](FeatureBitset features) {
|
||||
testPayNonexistent(features);
|
||||
testDirectRipple(features);
|
||||
testDirectRipple(features - featureNoRecipientLimit);
|
||||
testWithTransferFee(false, false, features);
|
||||
testWithTransferFee(false, true, features);
|
||||
testWithTransferFee(true, false, features);
|
||||
|
||||
@@ -2294,7 +2294,11 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
auto const postLimit = limitAmount(env, bob, gw, USD);
|
||||
BEAST_EXPECT(postLimit == preLimit);
|
||||
env(pay(alice, carol, USD(1)), ter(tecPATH_DRY));
|
||||
// carol already holds her 1000 limit; the issuer's step into
|
||||
// her is dry unless recipients are exempt from their limit.
|
||||
env(pay(alice, carol, USD(1)),
|
||||
ter(features[featureNoRecipientLimit] ? TER(tesSUCCESS)
|
||||
: TER(tecPATH_DRY)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2649,6 +2653,7 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
testTransferRate(features);
|
||||
testDisallowXRP(features);
|
||||
testLimitAmount(features);
|
||||
testLimitAmount(features - featureNoRecipientLimit);
|
||||
testURIUTF8(features);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,15 +206,21 @@ class DeliveredAmount_test : public beast::unit_test::suite
|
||||
env(pay(gw, alice, XRP(50)));
|
||||
checkDeliveredAmount.adjCountersSuccess();
|
||||
|
||||
// partial payment
|
||||
// Without recipient limits, the partial-payment flag does
|
||||
// not prevent the issuer from delivering the full amount.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
|
||||
checkDeliveredAmount.adjCountersPartialPayment();
|
||||
env.require(balance(bob, USD(1000)));
|
||||
env.require(balance(bob, USD(exempt ? 9999999 : 1000)));
|
||||
|
||||
// failed payment
|
||||
env(pay(bob, carol, USD(9999999)), ter(tecPATH_PARTIAL));
|
||||
checkDeliveredAmount.adjCountersFail();
|
||||
env.require(balance(carol, USD(0)));
|
||||
// bob is now fully funded only with the amendment enabled.
|
||||
env(pay(bob, carol, USD(9999999)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
if (exempt)
|
||||
checkDeliveredAmount.adjCountersSuccess();
|
||||
else
|
||||
checkDeliveredAmount.adjCountersFail();
|
||||
env.require(balance(carol, USD(exempt ? 9999999 : 0)));
|
||||
}
|
||||
|
||||
auto wsc = makeWSClient(env.app().config());
|
||||
@@ -285,15 +291,21 @@ class DeliveredAmount_test : public beast::unit_test::suite
|
||||
env(pay(gw, alice, XRP(50)));
|
||||
checkDeliveredAmount.adjCountersSuccess();
|
||||
|
||||
// partial payment
|
||||
// Without recipient limits, the partial-payment flag does
|
||||
// not prevent the issuer from delivering the full amount.
|
||||
bool const exempt = features[featureNoRecipientLimit];
|
||||
env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
|
||||
checkDeliveredAmount.adjCountersPartialPayment();
|
||||
env.require(balance(bob, USD(1000)));
|
||||
env.require(balance(bob, USD(exempt ? 9999999 : 1000)));
|
||||
|
||||
// failed payment
|
||||
env(pay(gw, carol, USD(9999999)), ter(tecPATH_PARTIAL));
|
||||
checkDeliveredAmount.adjCountersFail();
|
||||
env.require(balance(carol, USD(0)));
|
||||
// The issuer is likewise no longer capped by carol's limit.
|
||||
env(pay(gw, carol, USD(9999999)),
|
||||
ter(exempt ? TER(tesSUCCESS) : TER(tecPATH_PARTIAL)));
|
||||
if (exempt)
|
||||
checkDeliveredAmount.adjCountersSuccess();
|
||||
else
|
||||
checkDeliveredAmount.adjCountersFail();
|
||||
env.require(balance(carol, USD(exempt ? 9999999 : 0)));
|
||||
|
||||
env.close();
|
||||
std::string index;
|
||||
@@ -318,6 +330,8 @@ public:
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
testTxDeliveredAmountRPC(all);
|
||||
testAccountDeliveredAmountSubscribe(all);
|
||||
testTxDeliveredAmountRPC(all - featureNoRecipientLimit);
|
||||
testAccountDeliveredAmountSubscribe(all - featureNoRecipientLimit);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// Trust line limits are consulted here and in creditLimit2, by the payment
|
||||
// engine's direct step, which applies them to intermediary hops and (with
|
||||
// featureNoRecipientLimit) not to the issuer's step into the destination.
|
||||
// The only other reader is trustTransferLockedBalance, gated the same way.
|
||||
// A transactor that credits an account through accountSend or rippleCredit
|
||||
// never reads a limit; keep it that way so the invariant "a limit governs an
|
||||
// intermediary, never an account receiving its issuer's token" holds for
|
||||
// every transaction type without per-transactor exceptions.
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -44,6 +44,17 @@ protected:
|
||||
// Charge transfer fees when the prev step redeems
|
||||
Step const* const prevStep_ = nullptr;
|
||||
bool const isLast_;
|
||||
// This step is the delivered asset's issuer crediting the strand's
|
||||
// destination. Not the same as isLast_: the implied issuer-to-destination
|
||||
// step after a book or AMM is built without isLast, and isLast_ also
|
||||
// selects quality semantics. Not any step into the destination either: a
|
||||
// non-issuer crediting the destination is rippling the destination's
|
||||
// acceptance of that account's IOU, which its limit still governs. And
|
||||
// the currency must be the delivered one: the destination can appear
|
||||
// earlier in the same strand as an intermediary in another currency
|
||||
// (its own USD line feeding a USD/EUR book that delivers EUR), and that
|
||||
// hop is capped like any intermediary hop.
|
||||
bool const issuesToDst_;
|
||||
beast::Journal const j_;
|
||||
|
||||
struct Cache
|
||||
@@ -99,6 +110,9 @@ public:
|
||||
, currency_(c)
|
||||
, prevStep_(ctx.prevStep)
|
||||
, isLast_(ctx.isLast)
|
||||
, issuesToDst_(
|
||||
dst == ctx.strandDst && src == ctx.strandDeliver.account &&
|
||||
c == ctx.strandDeliver.currency)
|
||||
, j_(ctx.j)
|
||||
{
|
||||
}
|
||||
@@ -376,7 +390,25 @@ DirectIOfferCrossingStep::quality(ReadView const&, QualityDirection qDir) const
|
||||
std::pair<IOUAmount, DebtDirection>
|
||||
DirectIPaymentStep::maxFlow(ReadView const& sb, IOUAmount const&) const
|
||||
{
|
||||
return maxPaymentFlow(sb);
|
||||
auto const [amount, direction] = maxPaymentFlow(sb);
|
||||
|
||||
// A trust line limit governs an account being used as an intermediary,
|
||||
// not an account receiving its issuer's token. The issuer's step into the
|
||||
// strand's destination is not capped by the destination's limit. Only the
|
||||
// issuing direction is capped by a limit; when the source redeems,
|
||||
// `amount` is the source's own balance and stays as the cap.
|
||||
//
|
||||
// The cap is the largest IOU amount rather than `desired`: in the
|
||||
// reverse pass `desired` is the step's output, and a destination
|
||||
// QualityIn below one makes src->dst larger than the output, so an
|
||||
// uncapped step would be reported as limiting and the re-executed
|
||||
// limiting step would never agree with itself.
|
||||
if (issuesToDst_ && issues(direction) &&
|
||||
sb.rules().enabled(featureNoRecipientLimit))
|
||||
return {
|
||||
IOUAmount(STAmount::cMaxValue, STAmount::cMaxOffset), direction};
|
||||
|
||||
return {amount, direction};
|
||||
}
|
||||
|
||||
std::pair<IOUAmount, DebtDirection>
|
||||
@@ -441,6 +473,10 @@ DirectIPaymentStep::check(
|
||||
}
|
||||
}
|
||||
|
||||
// The destination's limit does not apply to its issuer's step into it
|
||||
// (see maxFlow); a dry test against it would refuse a payment the
|
||||
// recipient is allowed to receive.
|
||||
if (!(issuesToDst_ && ctx.view.rules().enabled(featureNoRecipientLimit)))
|
||||
{
|
||||
auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
|
||||
if (owed <= beast::zero)
|
||||
|
||||
@@ -564,8 +564,10 @@ Remit::doApply()
|
||||
|
||||
// if the target trustline doesn't exist we need to create it and
|
||||
// pay its reserve
|
||||
if (!sb.exists(
|
||||
keylet::line(dstAccID, issuerAccID, amount.getCurrency())))
|
||||
auto const lineKey =
|
||||
keylet::line(dstAccID, issuerAccID, amount.getCurrency());
|
||||
bool const lineExisted = sb.exists(lineKey);
|
||||
if (!lineExisted)
|
||||
{
|
||||
if (nativeRemit + objectReserve < nativeRemit)
|
||||
return tecINTERNAL;
|
||||
@@ -584,6 +586,22 @@ Remit::doApply()
|
||||
true);
|
||||
!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
// A line this remit created was never configured by the
|
||||
// destination. Mark the destination's side as persisting so the
|
||||
// line survives a zero balance until the destination clears it.
|
||||
if (!lineExisted && sb.rules().enabled(featureNoRecipientLimit))
|
||||
{
|
||||
if (auto const sleLine = sb.peek(lineKey))
|
||||
{
|
||||
bool const dstHigh = dstAccID > issuerAccID;
|
||||
sleLine->setFieldU32(
|
||||
sfFlags,
|
||||
sleLine->getFieldU32(sfFlags) |
|
||||
(dstHigh ? lsfHighPersist : lsfLowPersist));
|
||||
sb.update(sleLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,16 @@ SetTrust::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (uTxFlags & (tfSetPersist | tfClearPersist))
|
||||
{
|
||||
// Persist flags are valid only under the amendment, and not both.
|
||||
if (!ctx.rules.enabled(featureNoRecipientLimit) ||
|
||||
((uTxFlags & tfSetPersist) && (uTxFlags & tfClearPersist)))
|
||||
{
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount));
|
||||
|
||||
if (!isLegalNet(saLimitAmount))
|
||||
@@ -342,6 +352,8 @@ SetTrust::doApply()
|
||||
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
||||
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
||||
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
||||
bool const bSetPersist = (uTxFlags & tfSetPersist);
|
||||
bool const bClearPersist = (uTxFlags & tfClearPersist);
|
||||
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
@@ -508,6 +520,11 @@ SetTrust::doApply()
|
||||
uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
|
||||
if (bSetPersist)
|
||||
uFlagsOut |= (bHigh ? lsfHighPersist : lsfLowPersist);
|
||||
else if (bClearPersist)
|
||||
uFlagsOut &= ~(bHigh ? lsfHighPersist : lsfLowPersist);
|
||||
|
||||
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
|
||||
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||
uFlagsOut = computeFreezeFlags(
|
||||
@@ -531,14 +548,14 @@ SetTrust::doApply()
|
||||
|
||||
bool const bLowReserveSet = uLowQualityIn || uLowQualityOut ||
|
||||
((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple ||
|
||||
(uFlagsOut & lsfLowFreeze) || saLowLimit ||
|
||||
saLowBalance > beast::zero;
|
||||
(uFlagsOut & lsfLowFreeze) || (uFlagsOut & lsfLowPersist) ||
|
||||
saLowLimit || saLowBalance > beast::zero;
|
||||
bool const bLowReserveClear = !bLowReserveSet;
|
||||
|
||||
bool const bHighReserveSet = uHighQualityIn || uHighQualityOut ||
|
||||
((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple ||
|
||||
(uFlagsOut & lsfHighFreeze) || saHighLimit ||
|
||||
saHighBalance > beast::zero;
|
||||
(uFlagsOut & lsfHighFreeze) || (uFlagsOut & lsfHighPersist) ||
|
||||
saHighLimit || saHighBalance > beast::zero;
|
||||
bool const bHighReserveClear = !bHighReserveSet;
|
||||
|
||||
bool const bDefault = bLowReserveClear && bHighReserveClear;
|
||||
@@ -621,7 +638,7 @@ SetTrust::doApply()
|
||||
// setting default quality in.
|
||||
(!bQualityOut || !uQualityOut) && // Not setting quality out or
|
||||
// setting default quality out.
|
||||
(!bSetAuth))
|
||||
(!bSetAuth) && (!bSetPersist)) // Not asking the line to persist.
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "Redundant: Setting non-existent ripple line to defaults.";
|
||||
@@ -663,6 +680,18 @@ SetTrust::doApply()
|
||||
uQualityIn,
|
||||
uQualityOut,
|
||||
viewJ);
|
||||
|
||||
if (isTesSuccess(terResult) && bSetPersist)
|
||||
{
|
||||
if (auto const sleLine = view().peek(k))
|
||||
{
|
||||
sleLine->setFieldU32(
|
||||
sfFlags,
|
||||
sleLine->getFieldU32(sfFlags) |
|
||||
(bHigh ? lsfHighPersist : lsfLowPersist));
|
||||
view().update(sleLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return terResult;
|
||||
|
||||
@@ -1155,8 +1155,10 @@ trustTransferLockedBalance(
|
||||
}
|
||||
|
||||
// if final is more than dest limit and tx acct is not dest acct -
|
||||
// fail
|
||||
if (finalBalance > dstLimit && actingAccID != dstAccID)
|
||||
// fail. Under NoRecipientLimit a limit never governs the account
|
||||
// receiving, whoever finishes the instrument.
|
||||
if (finalBalance > dstLimit && actingAccID != dstAccID &&
|
||||
!view.rules().enabled(featureNoRecipientLimit))
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance would increase dest "
|
||||
|
||||
@@ -1127,10 +1127,15 @@ isTrustDefault(
|
||||
|
||||
const auto fNoRipple{high ? lsfHighNoRipple : lsfLowNoRipple};
|
||||
const auto fFreeze{high ? lsfHighFreeze : lsfLowFreeze};
|
||||
const auto fPersist{high ? lsfHighPersist : lsfLowPersist};
|
||||
|
||||
if (tlFlags & fFreeze)
|
||||
return false;
|
||||
|
||||
// A persisting side keeps its claim on the line at zero balance.
|
||||
if (tlFlags & fPersist)
|
||||
return false;
|
||||
|
||||
if ((acFlags & lsfDefaultRipple) && (tlFlags & fNoRipple))
|
||||
return false;
|
||||
|
||||
@@ -1268,6 +1273,8 @@ rippleCreditIOU(
|
||||
view.read(keylet::account(uSenderID))->getFlags() &
|
||||
lsfDefaultRipple) &&
|
||||
!(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
// Sender does not persist the line.
|
||||
!(uFlags & (!bSenderHigh ? lsfLowPersist : lsfHighPersist)) &&
|
||||
!sleRippleState->getFieldAmount(
|
||||
!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
@@ -1749,6 +1756,8 @@ updateTrustLine(
|
||||
flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
||||
static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
|
||||
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
// Sender does not persist the line.
|
||||
!(flags & (!bSenderHigh ? lsfLowPersist : lsfHighPersist)) &&
|
||||
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
|
||||
|
||||
Reference in New Issue
Block a user